summaryrefslogtreecommitdiff
path: root/contrib/fast-import
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/fast-import')
-rwxr-xr-xcontrib/fast-import/git-import.perl64
-rwxr-xr-xcontrib/fast-import/git-import.sh38
-rwxr-xr-xcontrib/fast-import/git-p41627
-rw-r--r--contrib/fast-import/git-p4.README12
-rw-r--r--contrib/fast-import/git-p4.bat1
-rw-r--r--contrib/fast-import/git-p4.txt159
-rwxr-xr-xcontrib/fast-import/import-directories.perl417
-rwxr-xr-xcontrib/fast-import/import-tars.perl84
-rwxr-xr-xcontrib/fast-import/import-zips.py73
9 files changed, 675 insertions, 1800 deletions
diff --git a/contrib/fast-import/git-import.perl b/contrib/fast-import/git-import.perl
new file mode 100755
index 0000000000..f9fef6db28
--- /dev/null
+++ b/contrib/fast-import/git-import.perl
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+#
+# Performs an initial import of a directory. This is the equivalent
+# of doing 'git init; git add .; git commit'. It's a little slower,
+# but is meant to be a simple fast-import example.
+
+use strict;
+use File::Find;
+
+my $USAGE = 'Usage: git-import branch import-message';
+my $branch = shift or die "$USAGE\n";
+my $message = shift or die "$USAGE\n";
+
+chomp(my $username = `git config user.name`);
+chomp(my $email = `git config user.email`);
+die 'You need to set user name and email'
+ unless $username && $email;
+
+system('git init');
+open(my $fi, '|-', qw(git fast-import --date-format=now))
+ or die "unable to spawn fast-import: $!";
+
+print $fi <<EOF;
+commit refs/heads/$branch
+committer $username <$email> now
+data <<MSGEOF
+$message
+MSGEOF
+
+EOF
+
+find(
+ sub {
+ if($File::Find::name eq './.git') {
+ $File::Find::prune = 1;
+ return;
+ }
+ return unless -f $_;
+
+ my $fn = $File::Find::name;
+ $fn =~ s#^.\/##;
+
+ open(my $in, '<', $_)
+ or die "unable to open $fn: $!";
+ my @st = stat($in)
+ or die "unable to stat $fn: $!";
+ my $len = $st[7];
+
+ print $fi "M 644 inline $fn\n";
+ print $fi "data $len\n";
+ while($len > 0) {
+ my $r = read($in, my $buf, $len < 4096 ? $len : 4096);
+ defined($r) or die "read error from $fn: $!";
+ $r > 0 or die "premature EOF from $fn: $!";
+ print $fi $buf;
+ $len -= $r;
+ }
+ print $fi "\n";
+
+ }, '.'
+);
+
+close($fi);
+exit $?;
diff --git a/contrib/fast-import/git-import.sh b/contrib/fast-import/git-import.sh
new file mode 100755
index 0000000000..0ca7718d05
--- /dev/null
+++ b/contrib/fast-import/git-import.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Performs an initial import of a directory. This is the equivalent
+# of doing 'git init; git add .; git commit'. It's a lot slower,
+# but is meant to be a simple fast-import example.
+
+if [ -z "$1" -o -z "$2" ]; then
+ echo "Usage: git-import branch import-message"
+ exit 1
+fi
+
+USERNAME="$(git config user.name)"
+EMAIL="$(git config user.email)"
+
+if [ -z "$USERNAME" -o -z "$EMAIL" ]; then
+ echo "You need to set user name and email"
+ exit 1
+fi
+
+git init
+
+(
+ cat <<EOF
+commit refs/heads/$1
+committer $USERNAME <$EMAIL> now
+data <<MSGEOF
+$2
+MSGEOF
+
+EOF
+ find * -type f|while read i;do
+ echo "M 100644 inline $i"
+ echo data $(stat -c '%s' "$i")
+ cat "$i"
+ echo
+ done
+ echo
+) | git fast-import --date-format=now
diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
deleted file mode 100755
index 55778c5775..0000000000
--- a/contrib/fast-import/git-p4
+++ /dev/null
@@ -1,1627 +0,0 @@
-#!/usr/bin/env python
-#
-# git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
-#
-# Author: Simon Hausmann <simon@lst.de>
-# Copyright: 2007 Simon Hausmann <simon@lst.de>
-# 2007 Trolltech ASA
-# License: MIT <http://www.opensource.org/licenses/mit-license.php>
-#
-
-import optparse, sys, os, marshal, popen2, subprocess, shelve
-import tempfile, getopt, sha, os.path, time, platform
-import re
-
-from sets import Set;
-
-verbose = False
-
-def die(msg):
- if verbose:
- raise Exception(msg)
- else:
- sys.stderr.write(msg + "\n")
- sys.exit(1)
-
-def write_pipe(c, str):
- if verbose:
- sys.stderr.write('Writing pipe: %s\n' % c)
-
- pipe = os.popen(c, 'w')
- val = pipe.write(str)
- if pipe.close():
- die('Command failed: %s' % c)
-
- return val
-
-def read_pipe(c, ignore_error=False):
- if verbose:
- sys.stderr.write('Reading pipe: %s\n' % c)
-
- pipe = os.popen(c, 'rb')
- val = pipe.read()
- if pipe.close() and not ignore_error:
- die('Command failed: %s' % c)
-
- return val
-
-
-def read_pipe_lines(c):
- if verbose:
- sys.stderr.write('Reading pipe: %s\n' % c)
- ## todo: check return status
- pipe = os.popen(c, 'rb')
- val = pipe.readlines()
- if pipe.close():
- die('Command failed: %s' % c)
-
- return val
-
-def system(cmd):
- if verbose:
- sys.stderr.write("executing %s\n" % cmd)
- if os.system(cmd) != 0:
- die("command failed: %s" % cmd)
-
-def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
- cmd = "p4 -G %s" % cmd
- if verbose:
- sys.stderr.write("Opening pipe: %s\n" % cmd)
-
- # Use a temporary file to avoid deadlocks without
- # subprocess.communicate(), which would put another copy
- # of stdout into memory.
- stdin_file = None
- if stdin is not None:
- stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
- stdin_file.write(stdin)
- stdin_file.flush()
- stdin_file.seek(0)
-
- p4 = subprocess.Popen(cmd, shell=True,
- stdin=stdin_file,
- stdout=subprocess.PIPE)
-
- result = []
- try:
- while True:
- entry = marshal.load(p4.stdout)
- result.append(entry)
- except EOFError:
- pass
- exitCode = p4.wait()
- if exitCode != 0:
- entry = {}
- entry["p4ExitCode"] = exitCode
- result.append(entry)
-
- return result
-
-def p4Cmd(cmd):
- list = p4CmdList(cmd)
- result = {}
- for entry in list:
- result.update(entry)
- return result;
-
-def p4Where(depotPath):
- if not depotPath.endswith("/"):
- depotPath += "/"
- output = p4Cmd("where %s..." % depotPath)
- if output["code"] == "error":
- return ""
- clientPath = ""
- if "path" in output:
- clientPath = output.get("path")
- elif "data" in output:
- data = output.get("data")
- lastSpace = data.rfind(" ")
- clientPath = data[lastSpace + 1:]
-
- if clientPath.endswith("..."):
- clientPath = clientPath[:-3]
- return clientPath
-
-def currentGitBranch():
- return read_pipe("git name-rev HEAD").split(" ")[1].strip()
-
-def isValidGitDir(path):
- if (os.path.exists(path + "/HEAD")
- and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
- return True;
- return False
-
-def parseRevision(ref):
- return read_pipe("git rev-parse %s" % ref).strip()
-
-def extractLogMessageFromGitCommit(commit):
- logMessage = ""
-
- ## fixme: title is first line of commit, not 1st paragraph.
- foundTitle = False
- for log in read_pipe_lines("git cat-file commit %s" % commit):
- if not foundTitle:
- if len(log) == 1:
- foundTitle = True
- continue
-
- logMessage += log
- return logMessage
-
-def extractSettingsGitLog(log):
- values = {}
- for line in log.split("\n"):
- line = line.strip()
- m = re.search (r"^ *\[git-p4: (.*)\]$", line)
- if not m:
- continue
-
- assignments = m.group(1).split (':')
- for a in assignments:
- vals = a.split ('=')
- key = vals[0].strip()
- val = ('='.join (vals[1:])).strip()
- if val.endswith ('\"') and val.startswith('"'):
- val = val[1:-1]
-
- values[key] = val
-
- paths = values.get("depot-paths")
- if not paths:
- paths = values.get("depot-path")
- if paths:
- values['depot-paths'] = paths.split(',')
- return values
-
-def gitBranchExists(branch):
- proc = subprocess.Popen(["git", "rev-parse", branch],
- stderr=subprocess.PIPE, stdout=subprocess.PIPE);
- return proc.wait() == 0;
-
-def gitConfig(key):
- return read_pipe("git config %s" % key, ignore_error=True).strip()
-
-def p4BranchesInGit(branchesAreInRemotes = True):
- branches = {}
-
- cmdline = "git rev-parse --symbolic "
- if branchesAreInRemotes:
- cmdline += " --remotes"
- else:
- cmdline += " --branches"
-
- for line in read_pipe_lines(cmdline):
- line = line.strip()
-
- ## only import to p4/
- if not line.startswith('p4/') or line == "p4/HEAD":
- continue
- branch = line
-
- # strip off p4
- branch = re.sub ("^p4/", "", line)
-
- branches[branch] = parseRevision(line)
- return branches
-
-def findUpstreamBranchPoint(head = "HEAD"):
- branches = p4BranchesInGit()
- # map from depot-path to branch name
- branchByDepotPath = {}
- for branch in branches.keys():
- tip = branches[branch]
- log = extractLogMessageFromGitCommit(tip)
- settings = extractSettingsGitLog(log)
- if settings.has_key("depot-paths"):
- paths = ",".join(settings["depot-paths"])
- branchByDepotPath[paths] = "remotes/p4/" + branch
-
- settings = None
- parent = 0
- while parent < 65535:
- commit = head + "~%s" % parent
- log = extractLogMessageFromGitCommit(commit)
- settings = extractSettingsGitLog(log)
- if settings.has_key("depot-paths"):
- paths = ",".join(settings["depot-paths"])
- if branchByDepotPath.has_key(paths):
- return [branchByDepotPath[paths], settings]
-
- parent = parent + 1
-
- return ["", settings]
-
-def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
- if not silent:
- print ("Creating/updating branch(es) in %s based on origin branch(es)"
- % localRefPrefix)
-
- originPrefix = "origin/p4/"
-
- for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
- line = line.strip()
- if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
- continue
-
- headName = line[len(originPrefix):]
- remoteHead = localRefPrefix + headName
- originHead = line
-
- original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
- if (not original.has_key('depot-paths')
- or not original.has_key('change')):
- continue
-
- update = False
- if not gitBranchExists(remoteHead):
- if verbose:
- print "creating %s" % remoteHead
- update = True
- else:
- settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
- if settings.has_key('change') > 0:
- if settings['depot-paths'] == original['depot-paths']:
- originP4Change = int(original['change'])
- p4Change = int(settings['change'])
- if originP4Change > p4Change:
- print ("%s (%s) is newer than %s (%s). "
- "Updating p4 branch from origin."
- % (originHead, originP4Change,
- remoteHead, p4Change))
- update = True
- else:
- print ("Ignoring: %s was imported from %s while "
- "%s was imported from %s"
- % (originHead, ','.join(original['depot-paths']),
- remoteHead, ','.join(settings['depot-paths'])))
-
- if update:
- system("git update-ref %s %s" % (remoteHead, originHead))
-
-def originP4BranchesExist():
- return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
-
-class Command:
- def __init__(self):
- self.usage = "usage: %prog [options]"
- self.needsGit = True
-
-class P4Debug(Command):
- def __init__(self):
- Command.__init__(self)
- self.options = [
- optparse.make_option("--verbose", dest="verbose", action="store_true",
- default=False),
- ]
- self.description = "A tool to debug the output of p4 -G."
- self.needsGit = False
- self.verbose = False
-
- def run(self, args):
- j = 0
- for output in p4CmdList(" ".join(args)):
- print 'Element: %d' % j
- j += 1
- print output
- return True
-
-class P4RollBack(Command):
- def __init__(self):
- Command.__init__(self)
- self.options = [
- optparse.make_option("--verbose", dest="verbose", action="store_true"),
- optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
- ]
- self.description = "A tool to debug the multi-branch import. Don't use :)"
- self.verbose = False
- self.rollbackLocalBranches = False
-
- def run(self, args):
- if len(args) != 1:
- return False
- maxChange = int(args[0])
-
- if "p4ExitCode" in p4Cmd("changes -m 1"):
- die("Problems executing p4");
-
- if self.rollbackLocalBranches:
- refPrefix = "refs/heads/"
- lines = read_pipe_lines("git rev-parse --symbolic --branches")
- else:
- refPrefix = "refs/remotes/"
- lines = read_pipe_lines("git rev-parse --symbolic --remotes")
-
- for line in lines:
- if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
- line = line.strip()
- ref = refPrefix + line
- log = extractLogMessageFromGitCommit(ref)
- settings = extractSettingsGitLog(log)
-
- depotPaths = settings['depot-paths']
- change = settings['change']
-
- changed = False
-
- if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
- for p in depotPaths]))) == 0:
- print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
- system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
- continue
-
- while change and int(change) > maxChange:
- changed = True
- if self.verbose:
- print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
- system("git update-ref %s \"%s^\"" % (ref, ref))
- log = extractLogMessageFromGitCommit(ref)
- settings = extractSettingsGitLog(log)
-
-
- depotPaths = settings['depot-paths']
- change = settings['change']
-
- if changed:
- print "%s rewound to %s" % (ref, change)
-
- return True
-
-class P4Submit(Command):
- def __init__(self):
- Command.__init__(self)
- self.options = [
- optparse.make_option("--continue", action="store_false", dest="firstTime"),
- optparse.make_option("--verbose", dest="verbose", action="store_true"),
- optparse.make_option("--origin", dest="origin"),
- optparse.make_option("--reset", action="store_true", dest="reset"),
- optparse.make_option("--log-substitutions", dest="substFile"),
- optparse.make_option("--dry-run", action="store_true"),
- optparse.make_option("--direct", dest="directSubmit", action="store_true"),
- optparse.make_option("--trust-me-like-a-fool", dest="trustMeLikeAFool", action="store_true"),
- ]
- self.description = "Submit changes from git to the perforce depot."
- self.usage += " [name of git branch to submit into perforce depot]"
- self.firstTime = True
- self.reset = False
- self.interactive = True
- self.dryRun = False
- self.substFile = ""
- self.firstTime = True
- self.origin = ""
- self.directSubmit = False
- self.trustMeLikeAFool = False
- self.verbose = False
- self.isWindows = (platform.system() == "Windows")
-
- self.logSubstitutions = {}
- self.logSubstitutions["<enter description here>"] = "%log%"
- self.logSubstitutions["\tDetails:"] = "\tDetails: %log%"
-
- def check(self):
- if len(p4CmdList("opened ...")) > 0:
- die("You have files opened with perforce! Close them before starting the sync.")
-
- def start(self):
- if len(self.config) > 0 and not self.reset:
- die("Cannot start sync. Previous sync config found at %s\n"
- "If you want to start submitting again from scratch "
- "maybe you want to call git-p4 submit --reset" % self.configFile)
-
- commits = []
- if self.directSubmit:
- commits.append("0")
- else:
- for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
- commits.append(line.strip())
- commits.reverse()
-
- self.config["commits"] = commits
-
- def prepareLogMessage(self, template, message):
- result = ""
-
- for line in template.split("\n"):
- if line.startswith("#"):
- result += line + "\n"
- continue
-
- substituted = False
- for key in self.logSubstitutions.keys():
- if line.find(key) != -1:
- value = self.logSubstitutions[key]
- value = value.replace("%log%", message)
- if value != "@remove@":
- result += line.replace(key, value) + "\n"
- substituted = True
- break
-
- if not substituted:
- result += line + "\n"
-
- return result
-
- def prepareSubmitTemplate(self):
- # remove lines in the Files section that show changes to files outside the depot path we're committing into
- template = ""
- inFilesSection = False
- for line in read_pipe_lines("p4 change -o"):
- if inFilesSection:
- if line.startswith("\t"):
- # path starts and ends with a tab
- path = line[1:]
- lastTab = path.rfind("\t")
- if lastTab != -1:
- path = path[:lastTab]
- if not path.startswith(self.depotPath):
- continue
- else:
- inFilesSection = False
- else:
- if line.startswith("Files:"):
- inFilesSection = True
-
- template += line
-
- return template
-
- def applyCommit(self, id):
- if self.directSubmit:
- print "Applying local change in working directory/index"
- diff = self.diffStatus
- else:
- print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
- diff = read_pipe_lines("git diff-tree -r --name-status \"%s^\" \"%s\"" % (id, id))
- filesToAdd = set()
- filesToDelete = set()
- editedFiles = set()
- for line in diff:
- modifier = line[0]
- path = line[1:].strip()
- if modifier == "M":
- system("p4 edit \"%s\"" % path)
- editedFiles.add(path)
- elif modifier == "A":
- filesToAdd.add(path)
- if path in filesToDelete:
- filesToDelete.remove(path)
- elif modifier == "D":
- filesToDelete.add(path)
- if path in filesToAdd:
- filesToAdd.remove(path)
- else:
- die("unknown modifier %s for %s" % (modifier, path))
-
- if self.directSubmit:
- diffcmd = "cat \"%s\"" % self.diffFile
- else:
- diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
- patchcmd = diffcmd + " | git apply "
- tryPatchCmd = patchcmd + "--check -"
- applyPatchCmd = patchcmd + "--check --apply -"
-
- if os.system(tryPatchCmd) != 0:
- print "Unfortunately applying the change failed!"
- print "What do you want to do?"
- response = "x"
- while response != "s" and response != "a" and response != "w":
- response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
- "and with .rej files / [w]rite the patch to a file (patch.txt) ")
- if response == "s":
- print "Skipping! Good luck with the next patches..."
- return
- elif response == "a":
- os.system(applyPatchCmd)
- if len(filesToAdd) > 0:
- print "You may also want to call p4 add on the following files:"
- print " ".join(filesToAdd)
- if len(filesToDelete):
- print "The following files should be scheduled for deletion with p4 delete:"
- print " ".join(filesToDelete)
- die("Please resolve and submit the conflict manually and "
- + "continue afterwards with git-p4 submit --continue")
- elif response == "w":
- system(diffcmd + " > patch.txt")
- print "Patch saved to patch.txt in %s !" % self.clientPath
- die("Please resolve and submit the conflict manually and "
- "continue afterwards with git-p4 submit --continue")
-
- system(applyPatchCmd)
-
- for f in filesToAdd:
- system("p4 add \"%s\"" % f)
- for f in filesToDelete:
- system("p4 revert \"%s\"" % f)
- system("p4 delete \"%s\"" % f)
-
- logMessage = ""
- if not self.directSubmit:
- logMessage = extractLogMessageFromGitCommit(id)
- logMessage = logMessage.replace("\n", "\n\t")
- if self.isWindows:
- logMessage = logMessage.replace("\n", "\r\n")
- logMessage = logMessage.strip()
-
- template = self.prepareSubmitTemplate()
-
- if self.interactive:
- submitTemplate = self.prepareLogMessage(template, logMessage)
- diff = read_pipe("p4 diff -du ...")
-
- for newFile in filesToAdd:
- diff += "==== new file ====\n"
- diff += "--- /dev/null\n"
- diff += "+++ %s\n" % newFile
- f = open(newFile, "r")
- for line in f.readlines():
- diff += "+" + line
- f.close()
-
- separatorLine = "######## everything below this line is just the diff #######"
- if platform.system() == "Windows":
- separatorLine += "\r"
- separatorLine += "\n"
-
- response = "e"
- if self.trustMeLikeAFool:
- response = "y"
-
- firstIteration = True
- while response == "e":
- if not firstIteration:
- response = raw_input("Do you want to submit this change? [y]es/[e]dit/[n]o/[s]kip ")
- firstIteration = False
- if response == "e":
- [handle, fileName] = tempfile.mkstemp()
- tmpFile = os.fdopen(handle, "w+")
- tmpFile.write(submitTemplate + separatorLine + diff)
- tmpFile.close()
- defaultEditor = "vi"
- if platform.system() == "Windows":
- defaultEditor = "notepad"
- editor = os.environ.get("EDITOR", defaultEditor);
- system(editor + " " + fileName)
- tmpFile = open(fileName, "rb")
- message = tmpFile.read()
- tmpFile.close()
- os.remove(fileName)
- submitTemplate = message[:message.index(separatorLine)]
- if self.isWindows:
- submitTemplate = submitTemplate.replace("\r\n", "\n")
-
- if response == "y" or response == "yes":
- if self.dryRun:
- print submitTemplate
- raw_input("Press return to continue...")
- else:
- if self.directSubmit:
- print "Submitting to git first"
- os.chdir(self.oldWorkingDirectory)
- write_pipe("git commit -a -F -", submitTemplate)
- os.chdir(self.clientPath)
-
- write_pipe("p4 submit -i", submitTemplate)
- elif response == "s":
- for f in editedFiles:
- system("p4 revert \"%s\"" % f);
- for f in filesToAdd:
- system("p4 revert \"%s\"" % f);
- system("rm %s" %f)
- for f in filesToDelete:
- system("p4 delete \"%s\"" % f);
- return
- else:
- print "Not submitting!"
- self.interactive = False
- else:
- fileName = "submit.txt"
- file = open(fileName, "w+")
- file.write(self.prepareLogMessage(template, logMessage))
- file.close()
- print ("Perforce submit template written as %s. "
- + "Please review/edit and then use p4 submit -i < %s to submit directly!"
- % (fileName, fileName))
-
- def run(self, args):
- if len(args) == 0:
- self.master = currentGitBranch()
- if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
- die("Detecting current git branch failed!")
- elif len(args) == 1:
- self.master = args[0]
- else:
- return False
-
- [upstream, settings] = findUpstreamBranchPoint()
- self.depotPath = settings['depot-paths'][0]
- if len(self.origin) == 0:
- self.origin = upstream
-
- if self.verbose:
- print "Origin branch is " + self.origin
-
- if len(self.depotPath) == 0:
- print "Internal error: cannot locate perforce depot path from existing branches"
- sys.exit(128)
-
- self.clientPath = p4Where(self.depotPath)
-
- if len(self.clientPath) == 0:
- print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
- sys.exit(128)
-
- print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
- self.oldWorkingDirectory = os.getcwd()
-
- if self.directSubmit:
- self.diffStatus = read_pipe_lines("git diff -r --name-status HEAD")
- if len(self.diffStatus) == 0:
- print "No changes in working directory to submit."
- return True
- patch = read_pipe("git diff -p --binary --diff-filter=ACMRTUXB HEAD")
- self.diffFile = self.gitdir + "/p4-git-diff"
- f = open(self.diffFile, "wb")
- f.write(patch)
- f.close();
-
- os.chdir(self.clientPath)
- response = raw_input("Do you want to sync %s with p4 sync? [y]es/[n]o " % self.clientPath)
- if response == "y" or response == "yes":
- system("p4 sync ...")
-
- if self.reset:
- self.firstTime = True
-
- if len(self.substFile) > 0:
- for line in open(self.substFile, "r").readlines():
- tokens = line.strip().split("=")
- self.logSubstitutions[tokens[0]] = tokens[1]
-
- self.check()
- self.configFile = self.gitdir + "/p4-git-sync.cfg"
- self.config = shelve.open(self.configFile, writeback=True)
-
- if self.firstTime:
- self.start()
-
- commits = self.config.get("commits", [])
-
- while len(commits) > 0:
- self.firstTime = False
- commit = commits[0]
- commits = commits[1:]
- self.config["commits"] = commits
- self.applyCommit(commit)
- if not self.interactive:
- break
-
- self.config.close()
-
- if self.directSubmit:
- os.remove(self.diffFile)
-
- if len(commits) == 0:
- if self.firstTime:
- print "No changes found to apply between %s and current HEAD" % self.origin
- else:
- print "All changes applied!"
- os.chdir(self.oldWorkingDirectory)
- response = raw_input("Do you want to sync from Perforce now using git-p4 rebase? [y]es/[n]o ")
- if response == "y" or response == "yes":
- rebase = P4Rebase()
- rebase.run([])
- os.remove(self.configFile)
-
- return True
-
-class P4Sync(Command):
- def __init__(self):
- Command.__init__(self)
- self.options = [
- optparse.make_option("--branch", dest="branch"),
- optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
- optparse.make_option("--changesfile", dest="changesFile"),
- optparse.make_option("--silent", dest="silent", action="store_true"),
- optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
- optparse.make_option("--verbose", dest="verbose", 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("--keep-path", dest="keepRepoPath", action='store_true',
- help="Keep entire BRANCH/DIR/SUBDIR prefix during import")
- ]
- self.description = """Imports from Perforce into a git repository.\n
- example:
- //depot/my/project/ -- to import the current head
- //depot/my/project/@all -- to import everything
- //depot/my/project/@1,6 -- to import only from revision 1 to 6
-
- (a ... is not needed in the path p4 specification, it's added implicitly)"""
-
- self.usage += " //depot/path[@revRange]"
- self.silent = False
- self.createdBranches = Set()
- self.committedChanges = Set()
- self.branch = ""
- self.detectBranches = False
- self.detectLabels = False
- self.changesFile = ""
- self.syncWithOrigin = True
- self.verbose = False
- self.importIntoRemotes = True
- self.maxChanges = ""
- self.isWindows = (platform.system() == "Windows")
- self.keepRepoPath = False
- self.depotPaths = None
- self.p4BranchesInGit = []
-
- if gitConfig("git-p4.syncFromOrigin") == "false":
- self.syncWithOrigin = False
-
- def extractFilesFromCommit(self, commit):
- files = []
- fnum = 0
- while commit.has_key("depotFile%s" % fnum):
- path = commit["depotFile%s" % fnum]
-
- found = [p for p in self.depotPaths
- if path.startswith (p)]
- if not found:
- fnum = fnum + 1
- continue
-
- file = {}
- file["path"] = path
- file["rev"] = commit["rev%s" % fnum]
- file["action"] = commit["action%s" % fnum]
- file["type"] = commit["type%s" % fnum]
- files.append(file)
- fnum = fnum + 1
- return files
-
- def stripRepoPath(self, path, prefixes):
- if self.keepRepoPath:
- prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
-
- for p in prefixes:
- if path.startswith(p):
- path = path[len(p):]
-
- return path
-
- def splitFilesIntoBranches(self, commit):
- branches = {}
- fnum = 0
- while commit.has_key("depotFile%s" % fnum):
- path = commit["depotFile%s" % fnum]
- found = [p for p in self.depotPaths
- if path.startswith (p)]
- if not found:
- fnum = fnum + 1
- continue
-
- file = {}
- file["path"] = path
- file["rev"] = commit["rev%s" % fnum]
- file["action"] = commit["action%s" % fnum]
- file["type"] = commit["type%s" % fnum]
- fnum = fnum + 1
-
- relPath = self.stripRepoPath(path, self.depotPaths)
-
- for branch in self.knownBranches.keys():
-
- # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
- if relPath.startswith(branch + "/"):
- if branch not in branches:
- branches[branch] = []
- branches[branch].append(file)
- break
-
- return branches
-
- ## Should move this out, doesn't use SELF.
- def readP4Files(self, files):
- files = [f for f in files
- if f['action'] != 'delete']
-
- if not files:
- return
-
- filedata = p4CmdList('-x - print',
- stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
- for f in files]),
- stdin_mode='w+')
- if "p4ExitCode" in filedata[0]:
- die("Problems executing p4. Error: [%d]."
- % (filedata[0]['p4ExitCode']));
-
- j = 0;
- contents = {}
- while j < len(filedata):
- stat = filedata[j]
- j += 1
- text = ''
- while j < len(filedata) and filedata[j]['code'] in ('text',
- 'binary'):
- text += filedata[j]['data']
- j += 1
-
-
- if not stat.has_key('depotFile'):
- sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
- continue
-
- contents[stat['depotFile']] = text
-
- for f in files:
- assert not f.has_key('data')
- f['data'] = contents[f['path']]
-
- def commit(self, details, files, branch, branchPrefixes, parent = ""):
- epoch = details["time"]
- author = details["user"]
-
- if self.verbose:
- print "commit into %s" % branch
-
- # start with reading files; if that fails, we should not
- # create a commit.
- new_files = []
- for f in files:
- if [p for p in branchPrefixes if f['path'].startswith(p)]:
- new_files.append (f)
- else:
- sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
- files = new_files
- self.readP4Files(files)
-
-
-
-
- self.gitStream.write("commit %s\n" % branch)
-# gitStream.write("mark :%s\n" % details["change"])
- self.committedChanges.add(int(details["change"]))
- committer = ""
- if author not in self.users:
- self.getUserMapFromPerforceServer()
- if author in self.users:
- committer = "%s %s %s" % (self.users[author], epoch, self.tz)
- else:
- committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
-
- self.gitStream.write("committer %s\n" % committer)
-
- self.gitStream.write("data <<EOT\n")
- self.gitStream.write(details["desc"])
- self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
- % (','.join (branchPrefixes), details["change"]))
- if len(details['options']) > 0:
- self.gitStream.write(": options = %s" % details['options'])
- self.gitStream.write("]\nEOT\n\n")
-
- if len(parent) > 0:
- if self.verbose:
- print "parent %s" % parent
- self.gitStream.write("from %s\n" % parent)
-
- for file in files:
- if file["type"] == "apple":
- print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
- continue
-
- relPath = self.stripRepoPath(file['path'], branchPrefixes)
- if file["action"] == "delete":
- self.gitStream.write("D %s\n" % relPath)
- else:
- data = file['data']
-
- mode = "644"
- if file["type"].startswith("x"):
- mode = "755"
- elif file["type"] == "symlink":
- mode = "120000"
- # p4 print on a symlink contains "target\n", so strip it off
- data = data[:-1]
-
- if self.isWindows and file["type"].endswith("text"):
- data = data.replace("\r\n", "\n")
-
- self.gitStream.write("M %s inline %s\n" % (mode, relPath))
- self.gitStream.write("data %s\n" % len(data))
- self.gitStream.write(data)
- self.gitStream.write("\n")
-
- self.gitStream.write("\n")
-
- change = int(details["change"])
-
- if self.labels.has_key(change):
- label = self.labels[change]
- labelDetails = label[0]
- labelRevisions = label[1]
- if self.verbose:
- print "Change %s is labelled %s" % (change, labelDetails)
-
- files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
- for p in branchPrefixes]))
-
- if len(files) == len(labelRevisions):
-
- cleanedFiles = {}
- for info in files:
- if info["action"] == "delete":
- continue
- cleanedFiles[info["depotFile"]] = info["rev"]
-
- if cleanedFiles == labelRevisions:
- self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
- self.gitStream.write("from %s\n" % branch)
-
- owner = labelDetails["Owner"]
- tagger = ""
- if author in self.users:
- tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
- else:
- tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
- self.gitStream.write("tagger %s\n" % tagger)
- self.gitStream.write("data <<EOT\n")
- self.gitStream.write(labelDetails["Description"])
- self.gitStream.write("EOT\n\n")
-
- else:
- if not self.silent:
- print ("Tag %s does not match with change %s: files do not match."
- % (labelDetails["label"], change))
-
- else:
- if not self.silent:
- print ("Tag %s does not match with change %s: file count is different."
- % (labelDetails["label"], change))
-
- def getUserCacheFilename(self):
- home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
- return home + "/.gitp4-usercache.txt"
-
- def getUserMapFromPerforceServer(self):
- if self.userMapFromPerforceServer:
- return
- self.users = {}
-
- for output in p4CmdList("users"):
- if not output.has_key("User"):
- continue
- self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
-
-
- s = ''
- for (key, val) in self.users.items():
- s += "%s\t%s\n" % (key, val)
-
- open(self.getUserCacheFilename(), "wb").write(s)
- self.userMapFromPerforceServer = True
-
- def loadUserMapFromCache(self):
- self.users = {}
- self.userMapFromPerforceServer = False
- try:
- cache = open(self.getUserCacheFilename(), "rb")
- lines = cache.readlines()
- cache.close()
- for line in lines:
- entry = line.strip().split("\t")
- self.users[entry[0]] = entry[1]
- except IOError:
- self.getUserMapFromPerforceServer()
-
- def getLabels(self):
- self.labels = {}
-
- l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
- if len(l) > 0 and not self.silent:
- print "Finding files belonging to labels in %s" % `self.depotPath`
-
- for output in l:
- label = output["label"]
- revisions = {}
- newestChange = 0
- if self.verbose:
- print "Querying files for label %s" % label
- for file in p4CmdList("files "
- + ' '.join (["%s...@%s" % (p, label)
- for p in self.depotPaths])):
- revisions[file["depotFile"]] = file["rev"]
- change = int(file["change"])
- if change > newestChange:
- newestChange = change
-
- self.labels[newestChange] = [output, revisions]
-
- if self.verbose:
- print "Label changes: %s" % self.labels.keys()
-
- def guessProjectName(self):
- for p in self.depotPaths:
- if p.endswith("/"):
- p = p[:-1]
- p = p[p.strip().rfind("/") + 1:]
- if not p.endswith("/"):
- p += "/"
- return p
-
- def getBranchMapping(self):
- lostAndFoundBranches = set()
-
- for info in p4CmdList("branches"):
- details = p4Cmd("branch -o %s" % info["branch"])
- viewIdx = 0
- while details.has_key("View%s" % viewIdx):
- paths = details["View%s" % viewIdx].split(" ")
- viewIdx = viewIdx + 1
- # require standard //depot/foo/... //depot/bar/... mapping
- if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
- continue
- source = paths[0]
- destination = paths[1]
- ## HACK
- if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
- source = source[len(self.depotPaths[0]):-4]
- destination = destination[len(self.depotPaths[0]):-4]
-
- if destination in self.knownBranches:
- if not self.silent:
- print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
- print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
- continue
-
- self.knownBranches[destination] = source
-
- lostAndFoundBranches.discard(destination)
-
- if source not in self.knownBranches:
- lostAndFoundBranches.add(source)
-
-
- for branch in lostAndFoundBranches:
- self.knownBranches[branch] = branch
-
- def listExistingP4GitBranches(self):
- # branches holds mapping from name to commit
- branches = p4BranchesInGit(self.importIntoRemotes)
- self.p4BranchesInGit = branches.keys()
- for branch in branches.keys():
- self.initialParents[self.refPrefix + branch] = branches[branch]
-
- def updateOptionDict(self, d):
- option_keys = {}
- if self.keepRepoPath:
- option_keys['keepRepoPath'] = 1
-
- d["options"] = ' '.join(sorted(option_keys.keys()))
-
- def readOptions(self, d):
- self.keepRepoPath = (d.has_key('options')
- and ('keepRepoPath' in d['options']))
-
- def run(self, args):
- self.depotPaths = []
- self.changeRange = ""
- self.initialParent = ""
- self.previousDepotPaths = []
-
- # map from branch depot path to parent branch
- self.knownBranches = {}
- self.initialParents = {}
- self.hasOrigin = originP4BranchesExist()
- if not self.syncWithOrigin:
- self.hasOrigin = False
-
- if self.importIntoRemotes:
- self.refPrefix = "refs/remotes/p4/"
- else:
- self.refPrefix = "refs/heads/p4/"
-
- if self.syncWithOrigin and self.hasOrigin:
- if not self.silent:
- print "Syncing with origin first by calling git fetch origin"
- system("git fetch origin")
-
- if len(self.branch) == 0:
- self.branch = self.refPrefix + "master"
- if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
- system("git update-ref %s refs/heads/p4" % self.branch)
- system("git branch -D p4");
- # create it /after/ importing, when master exists
- if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
- system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
-
- # TODO: should always look at previous commits,
- # merge with previous imports, if possible.
- if args == []:
- if self.hasOrigin:
- createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
- self.listExistingP4GitBranches()
-
- if len(self.p4BranchesInGit) > 1:
- if not self.silent:
- print "Importing from/into multiple branches"
- self.detectBranches = True
-
- if self.verbose:
- print "branches: %s" % self.p4BranchesInGit
-
- p4Change = 0
- for branch in self.p4BranchesInGit:
- logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
-
- settings = extractSettingsGitLog(logMsg)
-
- self.readOptions(settings)
- if (settings.has_key('depot-paths')
- and settings.has_key ('change')):
- change = int(settings['change']) + 1
- p4Change = max(p4Change, change)
-
- depotPaths = sorted(settings['depot-paths'])
- if self.previousDepotPaths == []:
- self.previousDepotPaths = depotPaths
- else:
- paths = []
- for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
- for i in range(0, min(len(cur), len(prev))):
- if cur[i] <> prev[i]:
- i = i - 1
- break
-
- paths.append (cur[:i + 1])
-
- self.previousDepotPaths = paths
-
- if p4Change > 0:
- self.depotPaths = sorted(self.previousDepotPaths)
- self.changeRange = "@%s,#head" % p4Change
- if not self.detectBranches:
- self.initialParent = parseRevision(self.branch)
- if not self.silent and not self.detectBranches:
- print "Performing incremental import into %s git branch" % self.branch
-
- if not self.branch.startswith("refs/"):
- self.branch = "refs/heads/" + self.branch
-
- if len(args) == 0 and self.depotPaths:
- if not self.silent:
- print "Depot paths: %s" % ' '.join(self.depotPaths)
- else:
- if self.depotPaths and self.depotPaths != args:
- print ("previous import used depot path %s and now %s was specified. "
- "This doesn't work!" % (' '.join (self.depotPaths),
- ' '.join (args)))
- sys.exit(1)
-
- self.depotPaths = sorted(args)
-
- self.revision = ""
- self.users = {}
-
- newPaths = []
- for p in self.depotPaths:
- if p.find("@") != -1:
- atIdx = p.index("@")
- self.changeRange = p[atIdx:]
- if self.changeRange == "@all":
- self.changeRange = ""
- elif ',' not in self.changeRange:
- self.revision = self.changeRange
- self.changeRange = ""
- p = p[:atIdx]
- elif p.find("#") != -1:
- hashIdx = p.index("#")
- self.revision = p[hashIdx:]
- p = p[:hashIdx]
- elif self.previousDepotPaths == []:
- self.revision = "#head"
-
- p = re.sub ("\.\.\.$", "", p)
- if not p.endswith("/"):
- p += "/"
-
- newPaths.append(p)
-
- self.depotPaths = newPaths
-
-
- self.loadUserMapFromCache()
- self.labels = {}
- if self.detectLabels:
- self.getLabels();
-
- if self.detectBranches:
- ## FIXME - what's a P4 projectName ?
- self.projectName = self.guessProjectName()
-
- if not self.hasOrigin:
- self.getBranchMapping();
- if self.verbose:
- print "p4-git branches: %s" % self.p4BranchesInGit
- print "initial parents: %s" % self.initialParents
- for b in self.p4BranchesInGit:
- if b != "master":
-
- ## FIXME
- b = b[len(self.projectName):]
- self.createdBranches.add(b)
-
- self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
-
- importProcess = subprocess.Popen(["git", "fast-import"],
- stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE);
- self.gitOutput = importProcess.stdout
- self.gitStream = importProcess.stdin
- self.gitError = importProcess.stderr
-
- if self.revision:
- print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), self.revision, self.branch)
-
- details = { "user" : "git perforce import user", "time" : int(time.time()) }
- details["desc"] = ("Initial import of %s from the state at revision %s"
- % (' '.join(self.depotPaths), self.revision))
- details["change"] = self.revision
- newestRevision = 0
-
- fileCnt = 0
- for info in p4CmdList("files "
- + ' '.join(["%s...%s"
- % (p, self.revision)
- for p in self.depotPaths])):
-
- if info['code'] == 'error':
- sys.stderr.write("p4 returned an error: %s\n"
- % info['data'])
- sys.exit(1)
-
-
- change = int(info["change"])
- if change > newestRevision:
- newestRevision = change
-
- if info["action"] == "delete":
- # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
- #fileCnt = fileCnt + 1
- continue
-
- for prop in ["depotFile", "rev", "action", "type" ]:
- details["%s%s" % (prop, fileCnt)] = info[prop]
-
- fileCnt = fileCnt + 1
-
- details["change"] = newestRevision
- self.updateOptionDict(details)
- try:
- self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
- except IOError:
- print "IO error with git fast-import. Is your git version recent enough?"
- print self.gitError.read()
-
- else:
- changes = []
-
- if len(self.changesFile) > 0:
- output = open(self.changesFile).readlines()
- changeSet = Set()
- for line in output:
- changeSet.add(int(line))
-
- for change in changeSet:
- changes.append(change)
-
- changes.sort()
- else:
- if self.verbose:
- print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
- self.changeRange)
- assert self.depotPaths
- output = read_pipe_lines("p4 changes " + ' '.join (["%s...%s" % (p, self.changeRange)
- for p in self.depotPaths]))
-
- for line in output:
- changeNum = line.split(" ")[1]
- changes.append(int(changeNum))
-
- changes.sort()
-
- if len(self.maxChanges) > 0:
- changes = changes[:min(int(self.maxChanges), len(changes))]
-
- if len(changes) == 0:
- if not self.silent:
- print "No changes to import!"
- return True
-
- if not self.silent and not self.detectBranches:
- print "Import destination: %s" % self.branch
-
- self.updatedBranches = set()
-
- cnt = 1
- for change in changes:
- description = p4Cmd("describe %s" % change)
- self.updateOptionDict(description)
-
- if not self.silent:
- sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
- sys.stdout.flush()
- cnt = cnt + 1
-
- try:
- if self.detectBranches:
- branches = self.splitFilesIntoBranches(description)
- for branch in branches.keys():
- ## HACK --hwn
- branchPrefix = self.depotPaths[0] + branch + "/"
-
- parent = ""
-
- filesForCommit = branches[branch]
-
- if self.verbose:
- print "branch is %s" % branch
-
- self.updatedBranches.add(branch)
-
- if branch not in self.createdBranches:
- self.createdBranches.add(branch)
- parent = self.knownBranches[branch]
- if parent == branch:
- parent = ""
- elif self.verbose:
- print "parent determined through known branches: %s" % parent
-
- # main branch? use master
- if branch == "main":
- branch = "master"
- else:
-
- ## FIXME
- branch = self.projectName + branch
-
- if parent == "main":
- parent = "master"
- elif len(parent) > 0:
- ## FIXME
- parent = self.projectName + parent
-
- branch = self.refPrefix + branch
- if len(parent) > 0:
- parent = self.refPrefix + parent
-
- if self.verbose:
- print "looking for initial parent for %s; current parent is %s" % (branch, parent)
-
- if len(parent) == 0 and branch in self.initialParents:
- parent = self.initialParents[branch]
- del self.initialParents[branch]
-
- self.commit(description, filesForCommit, branch, [branchPrefix], parent)
- else:
- files = self.extractFilesFromCommit(description)
- self.commit(description, files, self.branch, self.depotPaths,
- self.initialParent)
- self.initialParent = ""
- except IOError:
- print self.gitError.read()
- sys.exit(1)
-
- if not self.silent:
- print ""
- if len(self.updatedBranches) > 0:
- sys.stdout.write("Updated branches: ")
- for b in self.updatedBranches:
- sys.stdout.write("%s " % b)
- sys.stdout.write("\n")
-
-
- self.gitStream.close()
- if importProcess.wait() != 0:
- die("fast-import failed: %s" % self.gitError.read())
- self.gitOutput.close()
- self.gitError.close()
-
- return True
-
-class P4Rebase(Command):
- def __init__(self):
- Command.__init__(self)
- self.options = [ ]
- self.description = ("Fetches the latest revision from perforce and "
- + "rebases the current work (branch) against it")
- self.verbose = False
-
- def run(self, args):
- sync = P4Sync()
- sync.run([])
-
- [upstream, settings] = findUpstreamBranchPoint()
- if len(upstream) == 0:
- die("Cannot find upstream branchpoint for rebase")
-
- # the branchpoint may be p4/foo~3, so strip off the parent
- upstream = re.sub("~[0-9]+$", "", upstream)
-
- print "Rebasing the current branch onto %s" % upstream
- oldHead = read_pipe("git rev-parse HEAD").strip()
- system("git rebase %s" % upstream)
- system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
- return True
-
-class P4Clone(P4Sync):
- def __init__(self):
- P4Sync.__init__(self)
- self.description = "Creates a new git repository and imports from Perforce into it"
- self.usage = "usage: %prog [options] //depot/path[@revRange]"
- self.options.append(
- optparse.make_option("--destination", dest="cloneDestination",
- action='store', default=None,
- help="where to leave result of the clone"))
- self.cloneDestination = None
- self.needsGit = False
-
- def defaultDestination(self, args):
- ## TODO: use common prefix of args?
- depotPath = args[0]
- depotDir = re.sub("(@[^@]*)$", "", depotPath)
- depotDir = re.sub("(#[^#]*)$", "", depotDir)
- depotDir = re.sub(r"\.\.\.$,", "", depotDir)
- depotDir = re.sub(r"/$", "", depotDir)
- return os.path.split(depotDir)[1]
-
- def run(self, args):
- if len(args) < 1:
- return False
-
- if self.keepRepoPath and not self.cloneDestination:
- sys.stderr.write("Must specify destination for --keep-path\n")
- sys.exit(1)
-
- depotPaths = args
-
- if not self.cloneDestination and len(depotPaths) > 1:
- self.cloneDestination = depotPaths[-1]
- depotPaths = depotPaths[:-1]
-
- for p in depotPaths:
- if not p.startswith("//"):
- return False
-
- if not self.cloneDestination:
- self.cloneDestination = self.defaultDestination(args)
-
- print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
- if not os.path.exists(self.cloneDestination):
- os.makedirs(self.cloneDestination)
- os.chdir(self.cloneDestination)
- system("git init")
- self.gitdir = os.getcwd() + "/.git"
- if not P4Sync.run(self, depotPaths):
- return False
- if self.branch != "master":
- if gitBranchExists("refs/remotes/p4/master"):
- system("git branch master refs/remotes/p4/master")
- system("git checkout -f")
- else:
- print "Could not detect main branch. No checkout/master branch created."
-
- return True
-
-class P4Branches(Command):
- def __init__(self):
- Command.__init__(self)
- self.options = [ ]
- self.description = ("Shows the git branches that hold imports and their "
- + "corresponding perforce depot paths")
- self.verbose = False
-
- def run(self, args):
- if originP4BranchesExist():
- createOrUpdateBranchesFromOrigin()
-
- cmdline = "git rev-parse --symbolic "
- cmdline += " --remotes"
-
- for line in read_pipe_lines(cmdline):
- line = line.strip()
-
- if not line.startswith('p4/') or line == "p4/HEAD":
- continue
- branch = line
-
- log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
- settings = extractSettingsGitLog(log)
-
- print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
- return True
-
-class HelpFormatter(optparse.IndentedHelpFormatter):
- def __init__(self):
- optparse.IndentedHelpFormatter.__init__(self)
-
- def format_description(self, description):
- if description:
- return description + "\n"
- else:
- return ""
-
-def printUsage(commands):
- print "usage: %s <command> [options]" % sys.argv[0]
- print ""
- print "valid commands: %s" % ", ".join(commands)
- print ""
- print "Try %s <command> --help for command specific help." % sys.argv[0]
- print ""
-
-commands = {
- "debug" : P4Debug,
- "submit" : P4Submit,
- "sync" : P4Sync,
- "rebase" : P4Rebase,
- "clone" : P4Clone,
- "rollback" : P4RollBack,
- "branches" : P4Branches
-}
-
-
-def main():
- if len(sys.argv[1:]) == 0:
- printUsage(commands.keys())
- sys.exit(2)
-
- cmd = ""
- cmdName = sys.argv[1]
- try:
- klass = commands[cmdName]
- cmd = klass()
- except KeyError:
- print "unknown command %s" % cmdName
- print ""
- printUsage(commands.keys())
- sys.exit(2)
-
- options = cmd.options
- cmd.gitdir = os.environ.get("GIT_DIR", None)
-
- args = sys.argv[2:]
-
- if len(options) > 0:
- options.append(optparse.make_option("--git-dir", dest="gitdir"))
-
- parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
- options,
- description = cmd.description,
- formatter = HelpFormatter())
-
- (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
- global verbose
- verbose = cmd.verbose
- if cmd.needsGit:
- if cmd.gitdir == None:
- cmd.gitdir = os.path.abspath(".git")
- if not isValidGitDir(cmd.gitdir):
- cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
- if os.path.exists(cmd.gitdir):
- cdup = read_pipe("git rev-parse --show-cdup").strip()
- if len(cdup) > 0:
- os.chdir(cdup);
-
- if not isValidGitDir(cmd.gitdir):
- if isValidGitDir(cmd.gitdir + "/.git"):
- cmd.gitdir += "/.git"
- else:
- die("fatal: cannot locate git repository at %s" % cmd.gitdir)
-
- os.environ["GIT_DIR"] = cmd.gitdir
-
- if not cmd.run(args):
- parser.print_help()
-
-
-if __name__ == '__main__':
- main()
diff --git a/contrib/fast-import/git-p4.README b/contrib/fast-import/git-p4.README
new file mode 100644
index 0000000000..cec5ecfa7c
--- /dev/null
+++ b/contrib/fast-import/git-p4.README
@@ -0,0 +1,12 @@
+The git-p4 script moved to the top-level of the git source directory.
+
+Invoke it as any other git command, like "git p4 clone", for instance.
+
+Note that the top-level git-p4.py script is now the source. It is
+built using make to git-p4, which will be installed.
+
+Windows users can copy the git-p4.py source script directly, possibly
+invoking it through a batch file called "git-p4.bat" in the same folder.
+It should contain just one line:
+
+ @python "%~d0%~p0git-p4.py" %*
diff --git a/contrib/fast-import/git-p4.bat b/contrib/fast-import/git-p4.bat
deleted file mode 100644
index 9f97e884f5..0000000000
--- a/contrib/fast-import/git-p4.bat
+++ /dev/null
@@ -1 +0,0 @@
-@python "%~d0%~p0git-p4" %*
diff --git a/contrib/fast-import/git-p4.txt b/contrib/fast-import/git-p4.txt
deleted file mode 100644
index b16a8384bc..0000000000
--- a/contrib/fast-import/git-p4.txt
+++ /dev/null
@@ -1,159 +0,0 @@
-git-p4 - Perforce <-> Git converter using git-fast-import
-
-Usage
-=====
-
-git-p4 supports two main modes: Importing from Perforce to a Git repository is
-done using "git-p4 sync" or "git-p4 rebase". Submitting changes from Git back
-to Perforce is done using "git-p4 submit".
-
-Importing
-=========
-
-You can simply start with
-
- git-p4 clone //depot/path/project
-
-or
-
- git-p4 clone //depot/path/project myproject
-
-This will create an empty git repository in a subdirectory called "project" (or
-"myproject" with the second command), import the head revision from the
-specified perforce path into a git "p4" branch (remotes/p4 actually), create a
-master branch off it and check it out. If you want the entire history (not just
-the head revision) then you can simply append a "@all" to the depot path:
-
- git-p4 clone //depot/project/main@all myproject
-
-
-
-If you want more control you can also use the git-p4 sync command directly:
-
- mkdir repo-git
- cd repo-git
- git init
- git-p4 sync //path/in/your/perforce/depot
-
-This will import the current head revision of the specified depot path into a
-"remotes/p4/master" branch of your git repository. You can use the
---branch=mybranch option to use a different branch.
-
-If you want to import the entire history of a given depot path just use
-
- git-p4 sync //path/in/depot@all
-
-To achieve optimal compression you may want to run 'git repack -a -d -f' after
-a big import. This may take a while.
-
-Support for Perforce integrations is still work in progress. Don't bother
-trying it unless you want to hack on it :)
-
-Incremental Imports
-===================
-
-After an initial import you can easily synchronize your git repository with
-newer changes from the Perforce depot by just calling
-
- git-p4 sync
-
-in your git repository. By default the "remotes/p4/master" branch is updated.
-
-It is recommended to run 'git repack -a -d -f' from time to time when using
-incremental imports to optimally combine the individual git packs that each
-incremental import creates through the use of git-fast-import.
-
-
-A useful setup may be that you have a periodically updated git repository
-somewhere that contains a complete import of a Perforce project. That git
-repository can be used to clone the working repository from and one would
-import from Perforce directly after cloning using git-p4. If the connection to
-the Perforce server is slow and the working repository hasn't been synced for a
-while it may be desirable to fetch changes from the origin git repository using
-the efficient git protocol. git-p4 supports this setup by calling "git fetch origin"
-by default if there is an origin branch. You can disable this using
-
- git config git-p4.syncFromOrigin false
-
-Updating
-========
-
-A common working pattern is to fetch the latest changes from the Perforce depot
-and merge them with local uncommitted changes. The recommended way is to use
-git's rebase mechanism to preserve linear history. git-p4 provides a convenient
-
- git-p4 rebase
-
-command that calls git-p4 sync followed by git rebase to rebase the current
-working branch.
-
-Submitting
-==========
-
-git-p4 has support for submitting changes from a git repository back to the
-Perforce depot. This requires a Perforce checkout separate to your git
-repository. To submit all changes that are in the current git branch but not in
-the "p4" branch (or "origin" if "p4" doesn't exist) simply call
-
- git-p4 submit
-
-in your git repository. If you want to submit changes in a specific branch that
-is not your current git branch you can also pass that as an argument:
-
- git-p4 submit mytopicbranch
-
-You can override the reference branch with the --origin=mysourcebranch option.
-
-If a submit fails you may have to "p4 resolve" and submit manually. You can
-continue importing the remaining changes with
-
- git-p4 submit --continue
-
-After submitting you should sync your perforce import branch ("p4" or "origin")
-from Perforce using git-p4's sync command.
-
-If you have changes in your working directory that you haven't committed into
-git yet but that you want to commit to Perforce directly ("quick fixes") then
-you do not have to go through the intermediate step of creating a git commit
-first but you can just call
-
- git-p4 submit --direct
-
-
-Example
-=======
-
-# Clone a repository
- git-p4 clone //depot/path/project
-# Enter the newly cloned directory
- cd project
-# Do some work...
- vi foo.h
-# ... and commit locally to gi
- git commit foo.h
-# In the meantime somebody submitted changes to the Perforce depot. Rebase your latest
-# changes against the latest changes in Perforce:
- git-p4 rebase
-# Submit your locally committed changes back to Perforce
- git-p4 submit
-# ... and synchronize with Perforce
- git-p4 rebase
-
-
-Implementation Details...
-=========================
-
-* Changesets from Perforce are imported using git fast-import.
-* The import does not require anything from the Perforce client view as it just uses
- "p4 print //depot/path/file#revision" to get the actual file contents.
-* Every imported changeset has a special [git-p4...] line at the
- end of the log message that gives information about the corresponding
- Perforce change number and is also used by git-p4 itself to find out
- where to continue importing when doing incremental imports.
- Basically when syncing it extracts the perforce change number of the
- latest commit in the "p4" branch and uses "p4 changes //depot/path/...@changenum,#head"
- to find out which changes need to be imported.
-* git-p4 submit uses "git rev-list" to pick the commits between the "p4" branch
- and the current branch.
- The commits themselves are applied using git diff/format-patch ... | git apply
-
diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl
new file mode 100755
index 0000000000..7f3afa5ac4
--- /dev/null
+++ b/contrib/fast-import/import-directories.perl
@@ -0,0 +1,417 @@
+#!/usr/bin/perl
+#
+# Copyright 2008-2009 Peter Krefting <peter@softwolves.pp.se>
+#
+# ------------------------------------------------------------------------
+#
+# This program 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.
+#
+# 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, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# ------------------------------------------------------------------------
+
+=pod
+
+=head1 NAME
+
+import-directories - Import bits and pieces to Git.
+
+=head1 SYNOPSIS
+
+B<import-directories.perl> F<configfile> F<outputfile>
+
+=head1 DESCRIPTION
+
+Script to import arbitrary projects version controlled by the "copy the
+source directory to a new location and edit it there"-version controlled
+projects into version control. Handles projects with arbitrary branching
+and version trees, taking a file describing the inputs and generating a
+file compatible with the L<git-fast-import(1)> format.
+
+=head1 CONFIGURATION FILE
+
+=head2 Format
+
+The configuration file is based on the standard I<.ini> format.
+
+ ; Comments start with semi-colons
+ [section]
+ key=value
+
+Please see below for information on how to escape special characters.
+
+=head2 Global configuration
+
+Global configuration is done in the B<[config]> section, which should be
+the first section in the file. Configuration can be changed by
+repeating configuration sections later on.
+
+ [config]
+ ; configure conversion of CRLFs. "convert" means that all CRLFs
+ ; should be converted into LFs (suitable for the core.autocrlf
+ ; setting set to true in Git). "none" means that all data is
+ ; treated as binary.
+ crlf=convert
+
+=head2 Revision configuration
+
+Each revision that is to be imported is described in three
+sections. Revisions should be defined in topological order, so
+that a revision's parent has always been defined when a new revision
+is introduced. All the sections for one revision must be defined
+before defining the next revision.
+
+Each revision is assigned a unique numerical identifier. The
+numbers do not need to be consecutive, nor monotonically
+increasing.
+
+For instance, if your configuration file contains only the two
+revisions 4711 and 42, where 4711 is the initial commit, the
+only requirement is that 4711 is completely defined before 42.
+
+=pod
+
+=head3 Revision description section
+
+A section whose section name is just an integer gives meta-data
+about the revision.
+
+ [3]
+ ; author sets the author of the revisions
+ author=Peter Krefting <peter@softwolves.pp.se>
+ ; branch sets the branch that the revision should be committed to
+ branch=master
+ ; parent describes the revision that is the parent of this commit
+ ; (optional)
+ parent=1
+ ; merges describes a revision that is merged into this commit
+ ; (optional; can be repeated)
+ merges=2
+ ; selects one file to take the timestamp from
+ ; (optional; if unspecified, the most recent file from the .files
+ ; section is used)
+ timestamp=3/source.c
+
+=head3 Revision contents section
+
+A section whose section name is an integer followed by B<.files>
+describe all the files included in this revision. If a file that
+was available previously is not included in this revision, it will
+be removed.
+
+If an on-disk revision is incomplete, you can point to files from
+a previous revision. There are no restriction as to where the source
+files are located, nor to the names of them.
+
+ [3.files]
+ ; the key is the path inside the repository, the value is the path
+ ; as seen from the importer script.
+ source.c=ver-3.00/source.c
+ source.h=ver-2.99/source.h
+ readme.txt=ver-3.00/introduction to the project.txt
+
+File names are treated as byte strings (but please see below on
+quoting rules), and should be stored in the configuration file in
+the encoding that should be used in the generated repository.
+
+=head3 Revision commit message section
+
+A section whose section name is an integer followed by B<.message>
+gives the commit message. This section is read verbatim, up until
+the beginning of the next section. As such, a commit message may not
+contain a line that begins with an opening square bracket ("[") and
+ends with a closing square bracket ("]"), unless they are surrounded
+by whitespace or other characters.
+
+ [3.message]
+ Implement foobar.
+ ; trailing blank lines are ignored.
+
+=cut
+
+# Globals
+use strict;
+use warnings;
+use integer;
+my $crlfmode = 0;
+my @revs;
+my (%revmap, %message, %files, %author, %branch, %parent, %merges, %time, %timesource);
+my $sectiontype = 0;
+my $rev = 0;
+my $mark = 1;
+
+# Check command line
+if ($#ARGV < 1 || $ARGV[0] =~ /^--?h/)
+{
+ exec('perldoc', $0);
+ exit 1;
+}
+
+# Open configuration
+my $config = $ARGV[0];
+open CFG, '<', $config or die "Cannot open configuration file \"$config\": ";
+
+# Open output
+my $output = $ARGV[1];
+open OUT, '>', $output or die "Cannot create output file \"$output\": ";
+binmode OUT;
+
+LINE: while (my $line = <CFG>)
+{
+ $line =~ s/\r?\n$//;
+ next LINE if $sectiontype != 4 && $line eq '';
+ next LINE if $line =~ /^;/;
+ my $oldsectiontype = $sectiontype;
+ my $oldrev = $rev;
+
+ # Sections
+ if ($line =~ m"^\[(config|(\d+)(|\.files|\.message))\]$")
+ {
+ if ($1 eq 'config')
+ {
+ $sectiontype = 1;
+ }
+ elsif ($3 eq '')
+ {
+ $sectiontype = 2;
+ $rev = $2;
+ # Create a new revision
+ die "Duplicate rev: $line\n " if defined $revmap{$rev};
+ print "Reading revision $rev\n";
+ push @revs, $rev;
+ $revmap{$rev} = $mark ++;
+ $time{$revmap{$rev}} = 0;
+ }
+ elsif ($3 eq '.files')
+ {
+ $sectiontype = 3;
+ $rev = $2;
+ die "Revision mismatch: $line\n " unless $rev == $oldrev;
+ }
+ elsif ($3 eq '.message')
+ {
+ $sectiontype = 4;
+ $rev = $2;
+ die "Revision mismatch: $line\n " unless $rev == $oldrev;
+ }
+ else
+ {
+ die "Internal parse error: $line\n ";
+ }
+ next LINE;
+ }
+
+ # Parse data
+ if ($sectiontype != 4)
+ {
+ # Key and value
+ if ($line =~ m"^\s*([^\s].*=.*[^\s])\s*$")
+ {
+ my ($key, $value) = &parsekeyvaluepair($1);
+ # Global configuration
+ if (1 == $sectiontype)
+ {
+ if ($key eq 'crlf')
+ {
+ $crlfmode = 1, next LINE if $value eq 'convert';
+ $crlfmode = 0, next LINE if $value eq 'none';
+ }
+ die "Unknown configuration option: $line\n ";
+ }
+ # Revision specification
+ if (2 == $sectiontype)
+ {
+ my $current = $revmap{$rev};
+ $author{$current} = $value, next LINE if $key eq 'author';
+ $branch{$current} = $value, next LINE if $key eq 'branch';
+ $parent{$current} = $value, next LINE if $key eq 'parent';
+ $timesource{$current} = $value, next LINE if $key eq 'timestamp';
+ push(@{$merges{$current}}, $value), next LINE if $key eq 'merges';
+ die "Unknown revision option: $line\n ";
+ }
+ # Filespecs
+ if (3 == $sectiontype)
+ {
+ # Add the file and create a marker
+ die "File not found: $line\n " unless -f $value;
+ my $current = $revmap{$rev};
+ ${$files{$current}}{$key} = $mark;
+ my $time = &fileblob($value, $crlfmode, $mark ++);
+
+ # Update revision timestamp if more recent than other
+ # files seen, or if this is the file we have selected
+ # to take the time stamp from using the "timestamp"
+ # directive.
+ if ((defined $timesource{$current} && $timesource{$current} eq $value)
+ || $time > $time{$current})
+ {
+ $time{$current} = $time;
+ }
+ }
+ }
+ else
+ {
+ die "Parse error: $line\n ";
+ }
+ }
+ else
+ {
+ # Commit message
+ my $current = $revmap{$rev};
+ if (defined $message{$current})
+ {
+ $message{$current} .= "\n";
+ }
+ $message{$current} .= $line;
+ }
+}
+close CFG;
+
+# Start spewing out data for git-fast-import
+foreach my $commit (@revs)
+{
+ # Progress
+ print OUT "progress Creating revision $commit\n";
+
+ # Create commit header
+ my $mark = $revmap{$commit};
+
+ # Branch and commit id
+ print OUT "commit refs/heads/", $branch{$mark}, "\nmark :", $mark, "\n";
+
+ # Author and timestamp
+ die "No timestamp defined for $commit (no files?)\n" unless defined $time{$mark};
+ print OUT "committer ", $author{$mark}, " ", $time{$mark}, " +0100\n";
+
+ # Commit message
+ die "No message defined for $commit\n" unless defined $message{$mark};
+ my $message = $message{$mark};
+ $message =~ s/\n$//; # Kill trailing empty line
+ print OUT "data ", length($message), "\n", $message, "\n";
+
+ # Parent and any merges
+ print OUT "from :", $revmap{$parent{$mark}}, "\n" if defined $parent{$mark};
+ if (defined $merges{$mark})
+ {
+ foreach my $merge (@{$merges{$mark}})
+ {
+ print OUT "merge :", $revmap{$merge}, "\n";
+ }
+ }
+
+ # Output file marks
+ print OUT "deleteall\n"; # start from scratch
+ foreach my $file (sort keys %{$files{$mark}})
+ {
+ print OUT "M 644 :", ${$files{$mark}}{$file}, " $file\n";
+ }
+ print OUT "\n";
+}
+
+# Create one file blob
+sub fileblob
+{
+ my ($filename, $crlfmode, $mark) = @_;
+
+ # Import the file
+ print OUT "progress Importing $filename\nblob\nmark :$mark\n";
+ open FILE, '<', $filename or die "Cannot read $filename\n ";
+ binmode FILE;
+ my ($size, $mtime) = (stat(FILE))[7,9];
+ my $file;
+ read FILE, $file, $size;
+ close FILE;
+ $file =~ s/\r\n/\n/g if $crlfmode;
+ print OUT "data ", length($file), "\n", $file, "\n";
+
+ return $mtime;
+}
+
+# Parse a key=value pair
+sub parsekeyvaluepair
+{
+=pod
+
+=head2 Escaping special characters
+
+Key and value strings may be enclosed in quotes, in which case
+whitespace inside the quotes is preserved. Additionally, an equal
+sign may be included in the key by preceding it with a backslash.
+For example:
+
+ "key1 "=value1
+ key2=" value2"
+ key\=3=value3
+ key4=value=4
+ "key5""=value5
+
+Here the first key is "key1 " (note the trailing white-space) and the
+second value is " value2" (note the leading white-space). The third
+key contains an equal sign "key=3" and so does the fourth value, which
+does not need to be escaped. The fifth key contains a trailing quote,
+which does not need to be escaped since it is inside a surrounding
+quote.
+
+=cut
+ my $pair = shift;
+
+ # Separate key and value by the first non-quoted equal sign
+ my ($key, $value);
+ if ($pair =~ /^(.*[^\\])=(.*)$/)
+ {
+ ($key, $value) = ($1, $2)
+ }
+ else
+ {
+ die "Parse error: $pair\n ";
+ }
+
+ # Unquote and unescape the key and value separately
+ return (&unescape($key), &unescape($value));
+}
+
+# Unquote and unescape
+sub unescape
+{
+ my $string = shift;
+
+ # First remove enclosing quotes. Backslash before the trailing
+ # quote leaves both.
+ if ($string =~ /^"(.*[^\\])"$/)
+ {
+ $string = $1;
+ }
+
+ # Second remove any backslashes inside the unquoted string.
+ # For later: Handle special sequences like \t ?
+ $string =~ s/\\(.)/$1/g;
+
+ return $string;
+}
+
+__END__
+
+=pod
+
+=head1 EXAMPLES
+
+B<import-directories.perl> F<project.import>
+
+=head1 AUTHOR
+
+Copyright 2008-2009 Peter Krefting E<lt>peter@softwolves.pp.se>
+
+This program 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.
+
+=cut
diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl
index 23aeb257b9..95438e1ed4 100755
--- a/contrib/fast-import/import-tars.perl
+++ b/contrib/fast-import/import-tars.perl
@@ -8,19 +8,35 @@
## perl import-tars.perl *.tar.bz2
## git whatchanged import-tars
##
+## Use --metainfo to specify the extension for a meta data file, where
+## import-tars can read the commit message and optionally author and
+## committer information.
+##
+## echo 'This is the commit message' > myfile.tar.bz2.msg
+## perl import-tars.perl --metainfo=msg myfile.tar.bz2
use strict;
-die "usage: import-tars *.tar.{gz,bz2,Z}\n" unless @ARGV;
+use Getopt::Long;
+
+my $metaext = '';
+
+die "usage: import-tars [--metainfo=extension] *.tar.{gz,bz2,lzma,xz,Z}\n"
+ unless GetOptions('metainfo=s' => \$metaext) && @ARGV;
my $branch_name = 'import-tars';
my $branch_ref = "refs/heads/$branch_name";
-my $committer_name = 'T Ar Creator';
-my $committer_email = 'tar@example.com';
+my $author_name = $ENV{'GIT_AUTHOR_NAME'} || 'T Ar Creator';
+my $author_email = $ENV{'GIT_AUTHOR_EMAIL'} || 'tar@example.com';
+my $committer_name = $ENV{'GIT_COMMITTER_NAME'} || `git config --get user.name`;
+my $committer_email = $ENV{'GIT_COMMITTER_EMAIL'} || `git config --get user.email`;
+
+chomp($committer_name, $committer_email);
open(FI, '|-', 'git', 'fast-import', '--quiet')
or die "Unable to start git fast-import: $!\n";
foreach my $tar_file (@ARGV)
{
+ my $commit_time = time;
$tar_file =~ m,([^/]+)$,;
my $tar_name = $1;
@@ -33,13 +49,16 @@ foreach my $tar_file (@ARGV)
} elsif ($tar_name =~ s/\.tar\.Z$//) {
open(I, '-|', 'uncompress', '-c', $tar_file)
or die "Unable to uncompress -c $tar_file: $!\n";
+ } elsif ($tar_name =~ s/\.(tar\.(lzma|xz)|(tlz|txz))$//) {
+ open(I, '-|', 'xz', '-dc', $tar_file)
+ or die "Unable to xz -dc $tar_file: $!\n";
} elsif ($tar_name =~ s/\.tar$//) {
open(I, $tar_file) or die "Unable to open $tar_file: $!\n";
} else {
die "Unrecognized compression format: $tar_file\n";
}
- my $commit_time = 0;
+ my $author_time = 0;
my $next_mark = 1;
my $have_top_dir = 1;
my ($top_dir, %files);
@@ -77,10 +96,16 @@ foreach my $tar_file (@ARGV)
$mtime = oct $mtime;
next if $typeflag == 5; # directory
- print FI "blob\n", "mark :$next_mark\n", "data $size\n";
- while ($size > 0 && read(I, $_, 512) == 512) {
- print FI substr($_, 0, $size);
- $size -= 512;
+ print FI "blob\n", "mark :$next_mark\n";
+ if ($typeflag == 2) { # symbolic link
+ print FI "data ", length($linkname), "\n", $linkname;
+ $mode = 0120000;
+ } else {
+ print FI "data $size\n";
+ while ($size > 0 && read(I, $_, 512) == 512) {
+ print FI substr($_, 0, $size);
+ $size -= 512;
+ }
}
print FI "\n";
@@ -92,17 +117,49 @@ foreach my $tar_file (@ARGV)
}
$files{$path} = [$next_mark++, $mode];
- $commit_time = $mtime if $mtime > $commit_time;
+ $author_time = $mtime if $mtime > $author_time;
$path =~ m,^([^/]+)/,;
$top_dir = $1 unless $top_dir;
$have_top_dir = 0 if $top_dir ne $1;
}
+ my $commit_msg = "Imported from $tar_file.";
+ my $this_committer_name = $committer_name;
+ my $this_committer_email = $committer_email;
+ my $this_author_name = $author_name;
+ my $this_author_email = $author_email;
+ if ($metaext ne '') {
+ # Optionally read a commit message from <filename.tar>.msg
+ # Add a line on the form "Committer: name <e-mail>" to override
+ # the committer and "Author: name <e-mail>" to override the
+ # author for this tar ball.
+ if (open MSG, '<', "${tar_file}.${metaext}") {
+ my $header_done = 0;
+ $commit_msg = '';
+ while (<MSG>) {
+ if (!$header_done && /^Committer:\s+([^<>]*)\s+<(.*)>\s*$/i) {
+ $this_committer_name = $1;
+ $this_committer_email = $2;
+ } elsif (!$header_done && /^Author:\s+([^<>]*)\s+<(.*)>\s*$/i) {
+ $this_author_name = $1;
+ $this_author_email = $2;
+ } elsif (!$header_done && /^$/) { # empty line ends header.
+ $header_done = 1;
+ } else {
+ $commit_msg .= $_;
+ $header_done = 1;
+ }
+ }
+ close MSG;
+ }
+ }
+
print FI <<EOF;
commit $branch_ref
-committer $committer_name <$committer_email> $commit_time +0000
+author $this_author_name <$this_author_email> $author_time +0000
+committer $this_committer_name <$this_committer_email> $commit_time +0000
data <<END_OF_COMMIT_MESSAGE
-Imported from $tar_file.
+$commit_msg
END_OF_COMMIT_MESSAGE
deleteall
@@ -112,14 +169,15 @@ EOF
{
my ($mark, $mode) = @{$files{$path}};
$path =~ s,^([^/]+)/,, if $have_top_dir;
- printf FI "M %o :%i %s\n", $mode & 0111 ? 0755 : 0644, $mark, $path;
+ $mode = $mode & 0111 ? 0755 : 0644 unless $mode == 0120000;
+ printf FI "M %o :%i %s\n", $mode, $mark, $path;
}
print FI "\n";
print FI <<EOF;
tag $tar_name
from $branch_ref
-tagger $committer_name <$committer_email> $commit_time +0000
+tagger $author_name <$author_email> $author_time +0000
data <<END_OF_TAG_MESSAGE
Package $tar_name
END_OF_TAG_MESSAGE
diff --git a/contrib/fast-import/import-zips.py b/contrib/fast-import/import-zips.py
new file mode 100755
index 0000000000..82f5ed3ddc
--- /dev/null
+++ b/contrib/fast-import/import-zips.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+
+## zip archive frontend for git-fast-import
+##
+## For example:
+##
+## mkdir project; cd project; git init
+## python import-zips.py *.zip
+## git log --stat import-zips
+
+from os import popen, path
+from sys import argv, exit
+from time import mktime
+from zipfile import ZipFile
+
+if len(argv) < 2:
+ print 'Usage:', argv[0], '<zipfile>...'
+ exit(1)
+
+branch_ref = 'refs/heads/import-zips'
+committer_name = 'Z Ip Creator'
+committer_email = 'zip@example.com'
+
+fast_import = popen('git fast-import --quiet', 'w')
+def printlines(list):
+ for str in list:
+ fast_import.write(str + "\n")
+
+for zipfile in argv[1:]:
+ commit_time = 0
+ next_mark = 1
+ common_prefix = None
+ mark = dict()
+
+ zip = ZipFile(zipfile, 'r')
+ for name in zip.namelist():
+ if name.endswith('/'):
+ continue
+ info = zip.getinfo(name)
+
+ if commit_time < info.date_time:
+ commit_time = info.date_time
+ if common_prefix == None:
+ common_prefix = name[:name.rfind('/') + 1]
+ else:
+ while not name.startswith(common_prefix):
+ last_slash = common_prefix[:-1].rfind('/') + 1
+ common_prefix = common_prefix[:last_slash]
+
+ mark[name] = ':' + str(next_mark)
+ next_mark += 1
+
+ printlines(('blob', 'mark ' + mark[name], \
+ 'data ' + str(info.file_size)))
+ fast_import.write(zip.read(name) + "\n")
+
+ committer = committer_name + ' <' + committer_email + '> %d +0000' % \
+ mktime(commit_time + (0, 0, 0))
+
+ printlines(('commit ' + branch_ref, 'committer ' + committer, \
+ 'data <<EOM', 'Imported from ' + zipfile + '.', 'EOM', \
+ '', 'deleteall'))
+
+ for name in mark.keys():
+ fast_import.write('M 100644 ' + mark[name] + ' ' +
+ name[len(common_prefix):] + "\n")
+
+ printlines(('', 'tag ' + path.basename(zipfile), \
+ 'from ' + branch_ref, 'tagger ' + committer, \
+ 'data <<EOM', 'Package ' + zipfile, 'EOM', ''))
+
+if fast_import.close():
+ exit(1)