{"thread":{"id":"30321","subject":"[PATCHv4 0/5] git p4: label import/export: documentation fix","startedAt":"2012-04-24T08:33:18Z","lastAt":"2012-04-24T08:33:23Z","messageCount":6,"participants":["Luke Diamand"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"189956","messageId":"1335256403-22162-1-git-send-email-luke@diamand.org","threadId":"30321","inReplyTo":null,"subject":"[PATCHv4 0/5] git p4: label import/export: documentation fix","fromName":"Luke Diamand","fromEmail":"luke@diamand.org","sentAt":"2012-04-24T08:33:18Z","receivedAt":"2012-04-24T08:33:18Z","isPatch":false,"sender":{"key":"luke@diamand.org","avatar":"https://avatars.githubusercontent.com/u/5330967?v=4"},"body":"Sorry, the previous patch series contained an error in the \"git p4\"\ndocumentation. Everything else is as before.\n\nLuke Diamand (5):\n  git p4: Fixing script editor checks\n  git p4: Squash P4EDITOR in test harness\n  git p4: Ignore P4EDITOR if it is empty\n  git p4: import/export of labels to/from p4\n  git p4: move verbose to base class\n\n Documentation/git-p4.txt       |   54 ++++++--\n git-p4.py                      |  289 ++++++++++++++++++++++++++++++++--------\n t/lib-git-p4.sh                |    1 +\n t/t9800-git-p4-basic.sh        |    4 +-\n t/t9811-git-p4-label-import.sh |  202 ++++++++++++++++++++++++++++\n 5 files changed, 482 insertions(+), 68 deletions(-)\n create mode 100755 t/t9811-git-p4-label-import.sh\n\n-- \n1.7.10.8.g12781\n"},{"id":"189959","messageId":"1335256403-22162-2-git-send-email-luke@diamand.org","threadId":"30321","inReplyTo":"1335256403-22162-1-git-send-email-luke@diamand.org","subject":"[PATCHv4 1/5] git p4: Fixing script editor checks","fromName":"Luke Diamand","fromEmail":"luke@diamand.org","sentAt":"2012-04-24T08:33:19Z","receivedAt":"2012-04-24T08:33:19Z","isPatch":false,"sender":{"key":"luke@diamand.org","avatar":"https://avatars.githubusercontent.com/u/5330967?v=4"},"body":"If P4EDITOR is defined, the tests will fail when \"git p4\" starts an\neditor.\n\nSigned-off-by: Luke Diamand <luke@diamand.org>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t9800-git-p4-basic.sh |    4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh\nindex 13be144..b2f0869 100755\n--- a/t/t9800-git-p4-basic.sh\n+++ b/t/t9800-git-p4-basic.sh\n@@ -335,7 +335,7 @@ test_expect_success 'detect renames' '\n \ttest_when_finished cleanup_git &&\n \t(\n \t\tcd \"$git\" &&\n-\t\tgit config git-p4.skipSubmitEditCheck true &&\n+\t\tgit config git-p4.skipSubmitEdit true &&\n \n \t\tgit mv file1 file4 &&\n \t\tgit commit -a -m \"Rename file1 to file4\" &&\n@@ -394,7 +394,7 @@ test_expect_success 'detect copies' '\n \ttest_when_finished cleanup_git &&\n \t(\n \t\tcd \"$git\" &&\n-\t\tgit config git-p4.skipSubmitEditCheck true &&\n+\t\tgit config git-p4.skipSubmitEdit true &&\n \n \t\tcp file2 file8 &&\n \t\tgit add file8 &&\n-- \n1.7.10.8.g12781\n"},{"id":"189957","messageId":"1335256403-22162-3-git-send-email-luke@diamand.org","threadId":"30321","inReplyTo":"1335256403-22162-1-git-send-email-luke@diamand.org","subject":"[PATCHv4 2/5] git p4: Squash P4EDITOR in test harness","fromName":"Luke Diamand","fromEmail":"luke@diamand.org","sentAt":"2012-04-24T08:33:20Z","receivedAt":"2012-04-24T08:33:20Z","isPatch":false,"sender":{"key":"luke@diamand.org","avatar":"https://avatars.githubusercontent.com/u/5330967?v=4"},"body":"If P4EDITOR is set in the environment, test behavior could be\nunpredictable. Set it explicitly.\n\nSigned-off-by: Luke Diamand <luke@diamand.org>\n---\n t/lib-git-p4.sh |    1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh\nindex b90986c..121e380 100644\n--- a/t/lib-git-p4.sh\n+++ b/t/lib-git-p4.sh\n@@ -24,6 +24,7 @@ P4DPORT=$((10669 + ($testid - $git_p4_test_start)))\n \n export P4PORT=localhost:$P4DPORT\n export P4CLIENT=client\n+export P4EDITOR=:\n \n db=\"$TRASH_DIRECTORY/db\"\n cli=\"$TRASH_DIRECTORY/cli\"\n-- \n1.7.10.8.g12781\n"},{"id":"189961","messageId":"1335256403-22162-4-git-send-email-luke@diamand.org","threadId":"30321","inReplyTo":"1335256403-22162-1-git-send-email-luke@diamand.org","subject":"[PATCHv4 3/5] git p4: Ignore P4EDITOR if it is empty","fromName":"Luke Diamand","fromEmail":"luke@diamand.org","sentAt":"2012-04-24T08:33:21Z","receivedAt":"2012-04-24T08:33:21Z","isPatch":false,"sender":{"key":"luke@diamand.org","avatar":"https://avatars.githubusercontent.com/u/5330967?v=4"},"body":"p4 itself treats an empty value for P4EDITOR as the same as\nhaving P4EDITOR unset. Do the same for \"git p4\".\n\nSigned-off-by: Luke Diamand <luke@diamand.org>\n---\n git-p4.py |    2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/git-p4.py b/git-p4.py\nindex f910d5a..ead0c44 100755\n--- a/git-p4.py\n+++ b/git-p4.py\n@@ -970,7 +970,7 @@ class P4Submit(Command, P4UserMap):\n         mtime = os.stat(template_file).st_mtime\n \n         # invoke the editor\n-        if os.environ.has_key(\"P4EDITOR\"):\n+        if os.environ.has_key(\"P4EDITOR\") and (os.environ.get(\"P4EDITOR\") != \"\"):\n             editor = os.environ.get(\"P4EDITOR\")\n         else:\n             editor = read_pipe(\"git var GIT_EDITOR\").strip()\n-- \n1.7.10.8.g12781\n"},{"id":"189960","messageId":"1335256403-22162-5-git-send-email-luke@diamand.org","threadId":"30321","inReplyTo":"1335256403-22162-1-git-send-email-luke@diamand.org","subject":"[PATCHv4 4/5] git p4: import/export of labels to/from p4","fromName":"Luke Diamand","fromEmail":"luke@diamand.org","sentAt":"2012-04-24T08:33:22Z","receivedAt":"2012-04-24T08:33:22Z","isPatch":false,"sender":{"key":"luke@diamand.org","avatar":"https://avatars.githubusercontent.com/u/5330967?v=4"},"body":"The existing label import code looks at each commit being\nimported, and then checks for labels at that commit. This\ndoesn't work in the real world though because it will drop\nlabels applied on changelists that have already been imported,\na common pattern.\n\nThis change adds a new --import-labels option. With this option,\nat the end of the sync, git p4 gets sets of labels in p4 and git,\nand then creates a git tag for each missing p4 label.\n\nThis means that tags created on older changelists are\nstill imported.\n\nTags that could not be imported are added to an ignore\nlist.\n\nThe same sets of git and p4 tags and labels can also be used to\nderive a list of git tags to export to p4. This is enabled with\n--export-labels in 'git p4 submit'.\n\nSigned-off-by: Luke Diamand <luke@diamand.org>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-p4.txt       |   43 ++++++-\n git-p4.py                      |  260 ++++++++++++++++++++++++++++++++++------\n t/t9811-git-p4-label-import.sh |  202 +++++++++++++++++++++++++++++++\n 3 files changed, 466 insertions(+), 39 deletions(-)\n create mode 100755 t/t9811-git-p4-label-import.sh\n\ndiff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt\nindex 3fac413..01cc97b 100644\n--- a/Documentation/git-p4.txt\n+++ b/Documentation/git-p4.txt\n@@ -198,7 +198,11 @@ git repository:\n \n --detect-labels::\n \tQuery p4 for labels associated with the depot paths, and add\n-\tthem as tags in git.\n+\tthem as tags in git. Limited usefulness as only imports labels\n+\tassociated with new changelists. Deprecated.\n+\n+--import-labels::\n+\tImport labels from p4 into git.\n \n --import-local::\n \tBy default, p4 branches are stored in 'refs/remotes/p4/',\n@@ -263,6 +267,16 @@ These options can be used to modify 'git p4 submit' behavior.\n \tRe-author p4 changes before submitting to p4.  This option\n \trequires p4 admin privileges.\n \n+--export-labels::\n+\tExport tags from git as p4 labels. Tags found in git are applied\n+\tto the perforce working directory.\n+\n+Rebase options\n+~~~~~~~~~~~~~~\n+These options can be used to modify 'git p4 rebase' behavior.\n+\n+--import-labels::\n+\tImport p4 labels.\n \n DEPOT PATH SYNTAX\n -----------------\n@@ -427,11 +441,23 @@ git-p4.branchList::\n \tenabled.  Each entry should be a pair of branch names separated\n \tby a colon (:).  This example declares that both branchA and\n \tbranchB were created from main:\n+\n -------------\n git config       git-p4.branchList main:branchA\n git config --add git-p4.branchList main:branchB\n -------------\n \n+git-p4.ignoredP4Labels::\n+\tList of p4 labels to ignore. This is built automatically as\n+\tunimportable labels are discovered.\n+\n+git-p4.importLabels::\n+\tImport p4 labels into git, as per --import-labels.\n+\n+git-p4.labelImportRegexp::\n+\tOnly p4 labels matching this regular expression will be imported. The\n+\tdefault value is '[a-zA-Z0-9_\\-.]+$'.\n+\n git-p4.useClientSpec::\n \tSpecify that the p4 client spec should be used to identify p4\n \tdepot paths of interest.  This is equivalent to specifying the\n@@ -481,10 +507,17 @@ git-p4.skipUserNameCheck::\n \tsubmission regardless.\n \n git-p4.attemptRCSCleanup:\n-    If enabled, 'git p4 submit' will attempt to cleanup RCS keywords\n-    ($Header$, etc). These would otherwise cause merge conflicts and prevent\n-    the submit going ahead. This option should be considered experimental at\n-    present.\n+\tIf enabled, 'git p4 submit' will attempt to cleanup RCS keywords\n+\t($Header$, etc). These would otherwise cause merge conflicts and prevent\n+\tthe submit going ahead. This option should be considered experimental at\n+\tpresent.\n+\n+git-p4.exportLabels::\n+\tExport git tags to p4 labels, as per --export-labels.\n+\n+git-p4.labelExportRegexp::\n+\tOnly p4 labels matching this regular expression will be exported. The\n+\tdefault value is '[a-zA-Z0-9_\\-.]+$'.\n \n IMPLEMENTATION DETAILS\n ----------------------\ndiff --git a/git-p4.py b/git-p4.py\nindex ead0c44..6ce88ff 100755\n--- a/git-p4.py\n+++ b/git-p4.py\n@@ -14,6 +14,8 @@ import re, shutil\n \n verbose = False\n \n+# Only labels/tags matching this will be imported/exported\n+defaultLabelRegexp = r'[a-zA-Z0-9_\\-.]+$'\n \n def p4_build_cmd(cmd):\n     \"\"\"Build a suitable p4 command line.\n@@ -253,6 +255,26 @@ def getP4OpenedType(file):\n     else:\n         die(\"Could not determine file type for %s (result: '%s')\" % (file, result))\n \n+# Return the set of all p4 labels\n+def getP4Labels(depotPaths):\n+    labels = set()\n+    if isinstance(depotPaths,basestring):\n+        depotPaths = [depotPaths]\n+\n+    for l in p4CmdList([\"labels\"] + [\"%s...\" % p for p in depotPaths]):\n+        label = l['label']\n+        labels.add(label)\n+\n+    return labels\n+\n+# Return the set of all git tags\n+def getGitTags():\n+    gitTags = set()\n+    for line in read_pipe_lines([\"git\", \"tag\"]):\n+        tag = line.strip()\n+        gitTags.add(tag)\n+    return gitTags\n+\n def diffTreePattern():\n     # This is a simple generator for the diff tree regex pattern. This could be\n     # a class variable if this and parseDiffTreeEntry were a part of a class.\n@@ -792,6 +814,7 @@ class P4Submit(Command, P4UserMap):\n                 optparse.make_option(\"-M\", dest=\"detectRenames\", action=\"store_true\"),\n                 # preserve the user, requires relevant p4 permissions\n                 optparse.make_option(\"--preserve-user\", dest=\"preserveUser\", action=\"store_true\"),\n+                optparse.make_option(\"--export-labels\", dest=\"exportLabels\", action=\"store_true\"),\n         ]\n         self.description = \"Submit changes from git to the perforce depot.\"\n         self.usage += \" [name of git branch to submit into perforce depot]\"\n@@ -801,6 +824,7 @@ class P4Submit(Command, P4UserMap):\n         self.verbose = False\n         self.preserveUser = gitConfig(\"git-p4.preserveUser\").lower() == \"true\"\n         self.isWindows = (platform.system() == \"Windows\")\n+        self.exportLabels = False\n \n     def check(self):\n         if len(p4CmdList(\"opened ...\")) > 0:\n@@ -1228,6 +1252,71 @@ class P4Submit(Command, P4UserMap):\n                    + \"Please review/edit and then use p4 submit -i < %s to submit directly!\"\n                    % (fileName, fileName))\n \n+    # Export git tags as p4 labels. Create a p4 label and then tag\n+    # with that.\n+    def exportGitTags(self, gitTags):\n+        validLabelRegexp = gitConfig(\"git-p4.labelExportRegexp\")\n+        if len(validLabelRegexp) == 0:\n+            validLabelRegexp = defaultLabelRegexp\n+        m = re.compile(validLabelRegexp)\n+\n+        for name in gitTags:\n+\n+            if not m.match(name):\n+                if verbose:\n+                    print \"tag %s does not match regexp %s\" % (name, validTagRegexp)\n+                continue\n+\n+            # Get the p4 commit this corresponds to\n+            logMessage = extractLogMessageFromGitCommit(name)\n+            values = extractSettingsGitLog(logMessage)\n+\n+            if not values.has_key('change'):\n+                # a tag pointing to something not sent to p4; ignore\n+                if verbose:\n+                    print \"git tag %s does not give a p4 commit\" % name\n+                continue\n+            else:\n+                changelist = values['change']\n+\n+            # Get the tag details.\n+            inHeader = True\n+            isAnnotated = False\n+            body = []\n+            for l in read_pipe_lines([\"git\", \"cat-file\", \"-p\", name]):\n+                l = l.strip()\n+                if inHeader:\n+                    if re.match(r'tag\\s+', l):\n+                        isAnnotated = True\n+                    elif re.match(r'\\s*$', l):\n+                        inHeader = False\n+                        continue\n+                else:\n+                    body.append(l)\n+\n+            if not isAnnotated:\n+                body = [\"lightweight tag imported by git p4\\n\"]\n+\n+            # Create the label - use the same view as the client spec we are using\n+            clientSpec = getClientSpec()\n+\n+            labelTemplate  = \"Label: %s\\n\" % name\n+            labelTemplate += \"Description:\\n\"\n+            for b in body:\n+                labelTemplate += \"\\t\" + b + \"\\n\"\n+            labelTemplate += \"View:\\n\"\n+            for mapping in clientSpec.mappings:\n+                labelTemplate += \"\\t%s\\n\" % mapping.depot_side.path\n+\n+            p4_write_pipe([\"label\", \"-i\"], labelTemplate)\n+\n+            # Use the label\n+            p4_system([\"tag\", \"-l\", name] +\n+                      [\"%s@%s\" % (mapping.depot_side.path, changelist) for mapping in clientSpec.mappings])\n+\n+            if verbose:\n+                print \"created p4 label for tag %s\" % name\n+\n     def run(self, args):\n         if len(args) == 0:\n             self.master = currentGitBranch()\n@@ -1317,6 +1406,16 @@ class P4Submit(Command, P4UserMap):\n             rebase = P4Rebase()\n             rebase.rebase()\n \n+        if gitConfig(\"git-p4.exportLabels\", \"--bool\") == \"true\":\n+            self.exportLabels = true\n+\n+        if self.exportLabels:\n+            p4Labels = getP4Labels(self.depotPath)\n+            gitTags = getGitTags()\n+\n+            missingGitTags = gitTags - p4Labels\n+            self.exportGitTags(missingGitTags)\n+\n         return True\n \n class View(object):\n@@ -1544,6 +1643,7 @@ class P4Sync(Command, P4UserMap):\n                 optparse.make_option(\"--changesfile\", dest=\"changesFile\"),\n                 optparse.make_option(\"--silent\", dest=\"silent\", action=\"store_true\"),\n                 optparse.make_option(\"--detect-labels\", dest=\"detectLabels\", action=\"store_true\"),\n+                optparse.make_option(\"--import-labels\", dest=\"importLabels\", action=\"store_true\"),\n                 optparse.make_option(\"--verbose\", dest=\"verbose\", action=\"store_true\"),\n                 optparse.make_option(\"--import-local\", dest=\"importIntoRemotes\", action=\"store_false\",\n                                      help=\"Import into refs/heads/ , not refs/remotes\"),\n@@ -1568,6 +1668,7 @@ class P4Sync(Command, P4UserMap):\n         self.branch = \"\"\n         self.detectBranches = False\n         self.detectLabels = False\n+        self.importLabels = False\n         self.changesFile = \"\"\n         self.syncWithOrigin = True\n         self.verbose = False\n@@ -1829,6 +1930,38 @@ class P4Sync(Command, P4UserMap):\n         else:\n             return \"%s <a@b>\" % userid\n \n+    # Stream a p4 tag\n+    def streamTag(self, gitStream, labelName, labelDetails, commit, epoch):\n+        if verbose:\n+            print \"writing tag %s for commit %s\" % (labelName, commit)\n+        gitStream.write(\"tag %s\\n\" % labelName)\n+        gitStream.write(\"from %s\\n\" % commit)\n+\n+        if labelDetails.has_key('Owner'):\n+            owner = labelDetails[\"Owner\"]\n+        else:\n+            owner = None\n+\n+        # Try to use the owner of the p4 label, or failing that,\n+        # the current p4 user id.\n+        if owner:\n+            email = self.make_email(owner)\n+        else:\n+            email = self.make_email(self.p4UserId())\n+        tagger = \"%s %s %s\" % (email, epoch, self.tz)\n+\n+        gitStream.write(\"tagger %s\\n\" % tagger)\n+\n+        print \"labelDetails=\",labelDetails\n+        if labelDetails.has_key('Description'):\n+            description = labelDetails['Description']\n+        else:\n+            description = 'Label from git p4'\n+\n+        gitStream.write(\"data %d\\n\" % len(description))\n+        gitStream.write(description)\n+        gitStream.write(\"\\n\")\n+\n     def commit(self, details, files, branch, branchPrefixes, parent = \"\"):\n         epoch = details[\"time\"]\n         author = details[\"user\"]\n@@ -1893,25 +2026,7 @@ class P4Sync(Command, P4UserMap):\n                     cleanedFiles[info[\"depotFile\"]] = info[\"rev\"]\n \n                 if cleanedFiles == labelRevisions:\n-                    self.gitStream.write(\"tag tag_%s\\n\" % labelDetails[\"label\"])\n-                    self.gitStream.write(\"from %s\\n\" % branch)\n-\n-                    owner = labelDetails[\"Owner\"]\n-\n-                    # Try to use the owner of the p4 label, or failing that,\n-                    # the current p4 user id.\n-                    if owner:\n-                        email = self.make_email(owner)\n-                    else:\n-                        email = self.make_email(self.p4UserId())\n-                    tagger = \"%s %s %s\" % (email, epoch, self.tz)\n-\n-                    self.gitStream.write(\"tagger %s\\n\" % tagger)\n-\n-                    description = labelDetails[\"Description\"]\n-                    self.gitStream.write(\"data %d\\n\" % len(description))\n-                    self.gitStream.write(description)\n-                    self.gitStream.write(\"\\n\")\n+                    self.streamTag(self.gitStream, 'tag_%s' % labelDetails['label'], labelDetails, branch, epoch)\n \n                 else:\n                     if not self.silent:\n@@ -1923,6 +2038,7 @@ class P4Sync(Command, P4UserMap):\n                     print (\"Tag %s does not match with change %s: file count is different.\"\n                            % (labelDetails[\"label\"], change))\n \n+    # Build a dictionary of changelists and labels, for \"detect-labels\" option.\n     def getLabels(self):\n         self.labels = {}\n \n@@ -1949,6 +2065,69 @@ class P4Sync(Command, P4UserMap):\n         if self.verbose:\n             print \"Label changes: %s\" % self.labels.keys()\n \n+    # Import p4 labels as git tags. A direct mapping does not\n+    # exist, so assume that if all the files are at the same revision\n+    # then we can use that, or it's something more complicated we should\n+    # just ignore.\n+    def importP4Labels(self, stream, p4Labels):\n+        if verbose:\n+            print \"import p4 labels: \" + ' '.join(p4Labels)\n+\n+        ignoredP4Labels = gitConfigList(\"git-p4.ignoredP4Labels\")\n+        validLabelRegexp = gitConfig(\"git-p4.labelImportRegexp\")\n+        if len(validLabelRegexp) == 0:\n+            validLabelRegexp = defaultLabelRegexp\n+        m = re.compile(validLabelRegexp)\n+\n+        for name in p4Labels:\n+            commitFound = False\n+\n+            if not m.match(name):\n+                if verbose:\n+                    print \"label %s does not match regexp %s\" % (name,validLabelRegexp)\n+                continue\n+\n+            if name in ignoredP4Labels:\n+                continue\n+\n+            labelDetails = p4CmdList(['label', \"-o\", name])[0]\n+\n+            # get the most recent changelist for each file in this label\n+            change = p4Cmd([\"changes\", \"-m\", \"1\"] + [\"%s...@%s\" % (p, name)\n+                                for p in self.depotPaths])\n+\n+            if change.has_key('change'):\n+                # find the corresponding git commit; take the oldest commit\n+                changelist = int(change['change'])\n+                gitCommit = read_pipe([\"git\", \"rev-list\", \"--max-count=1\",\n+                     \"--reverse\", \":/\\[git-p4:.*change = %d\\]\" % changelist])\n+                if len(gitCommit) == 0:\n+                    print \"could not find git commit for changelist %d\" % changelist\n+                else:\n+                    gitCommit = gitCommit.strip()\n+                    commitFound = True\n+                    # Convert from p4 time format\n+                    try:\n+                        tmwhen = time.strptime(labelDetails['Update'], \"%Y/%m/%d %H:%M:%S\")\n+                    except ValueError:\n+                        print \"Could not convert label time %s\" % labelDetail['Update']\n+                        tmwhen = 1\n+\n+                    when = int(time.mktime(tmwhen))\n+                    self.streamTag(stream, name, labelDetails, gitCommit, when)\n+                    if verbose:\n+                        print \"p4 label %s mapped to git commit %s\" % (name, gitCommit)\n+            else:\n+                if verbose:\n+                    print \"Label %s has no changelists - possibly deleted?\" % name\n+\n+            if not commitFound:\n+                # We can't import this label; don't try again as it will get very\n+                # expensive repeatedly fetching all the files for labels that will\n+                # never be imported. If the label is moved in the future, the\n+                # ignore will need to be removed manually.\n+                system([\"git\", \"config\", \"--add\", \"git-p4.ignoredP4Labels\", name])\n+\n     def guessProjectName(self):\n         for p in self.depotPaths:\n             if p.endswith(\"/\"):\n@@ -2425,7 +2604,6 @@ class P4Sync(Command, P4UserMap):\n \n         self.depotPaths = newPaths\n \n-\n         self.loadUserMapFromCache()\n         self.labels = {}\n         if self.detectLabels:\n@@ -2489,22 +2667,31 @@ class P4Sync(Command, P4UserMap):\n             if len(changes) == 0:\n                 if not self.silent:\n                     print \"No changes to import!\"\n-                return True\n+            else:\n+                if not self.silent and not self.detectBranches:\n+                    print \"Import destination: %s\" % self.branch\n \n-            if not self.silent and not self.detectBranches:\n-                print \"Import destination: %s\" % self.branch\n+                self.updatedBranches = set()\n \n-            self.updatedBranches = set()\n+                self.importChanges(changes)\n \n-            self.importChanges(changes)\n+                if not self.silent:\n+                    print \"\"\n+                    if len(self.updatedBranches) > 0:\n+                        sys.stdout.write(\"Updated branches: \")\n+                        for b in self.updatedBranches:\n+                            sys.stdout.write(\"%s \" % b)\n+                        sys.stdout.write(\"\\n\")\n \n-            if not self.silent:\n-                print \"\"\n-                if len(self.updatedBranches) > 0:\n-                    sys.stdout.write(\"Updated branches: \")\n-                    for b in self.updatedBranches:\n-                        sys.stdout.write(\"%s \" % b)\n-                    sys.stdout.write(\"\\n\")\n+        if gitConfig(\"git-p4.importLabels\", \"--bool\") == \"true\":\n+            self.importLabels = true\n+\n+        if self.importLabels:\n+            p4Labels = getP4Labels(self.depotPaths)\n+            gitTags = getGitTags()\n+\n+            missingP4Labels = p4Labels - gitTags\n+            self.importP4Labels(self.gitStream, missingP4Labels)\n \n         self.gitStream.close()\n         if importProcess.wait() != 0:\n@@ -2523,13 +2710,18 @@ class P4Sync(Command, P4UserMap):\n class P4Rebase(Command):\n     def __init__(self):\n         Command.__init__(self)\n-        self.options = [ ]\n+        self.options = [\n+                optparse.make_option(\"--import-labels\", dest=\"importLabels\", action=\"store_true\"),\n+                optparse.make_option(\"--verbose\", dest=\"verbose\", action=\"store_true\"),\n+        ]\n+        self.verbose = False\n+        self.importLabels = False\n         self.description = (\"Fetches the latest revision from perforce and \"\n                             + \"rebases the current work (branch) against it\")\n-        self.verbose = False\n \n     def run(self, args):\n         sync = P4Sync()\n+        sync.importLabels = self.importLabels\n         sync.run([])\n \n         return self.rebase()\ndiff --git a/t/t9811-git-p4-label-import.sh b/t/t9811-git-p4-label-import.sh\nnew file mode 100755\nindex 0000000..fb00ffa\n--- /dev/null\n+++ b/t/t9811-git-p4-label-import.sh\n@@ -0,0 +1,202 @@\n+#!/bin/sh\n+\n+test_description='git p4 label tests'\n+\n+. ./lib-git-p4.sh\n+\n+test_expect_success 'start p4d' '\n+\tstart_p4d\n+'\n+\n+# Basic p4 label import tests.\n+#\n+test_expect_success 'basic p4 labels' '\n+\ttest_when_finished cleanup_git &&\n+\t(\n+\t\tcd \"$cli\" &&\n+\t\tmkdir -p main &&\n+\n+\t\techo f1 >main/f1 &&\n+\t\tp4 add main/f1 &&\n+\t\tp4 submit -d \"main/f1\" &&\n+\n+\t\techo f2 >main/f2 &&\n+\t\tp4 add main/f2 &&\n+\t\tp4 submit -d \"main/f2\" &&\n+\n+\t\techo f3 >main/file_with_\\$metachar &&\n+\t\tp4 add main/file_with_\\$metachar &&\n+\t\tp4 submit -d \"file with metachar\" &&\n+\n+\t\tp4 tag -l TAG_F1_ONLY main/f1 &&\n+\t\tp4 tag -l TAG_WITH\\$_SHELL_CHAR main/... &&\n+\t\tp4 tag -l this_tag_will_be\\ skipped main/... &&\n+\n+\t\techo f4 >main/f4 &&\n+\t\tp4 add main/f4 &&\n+\t\tp4 submit -d \"main/f4\" &&\n+\n+\t\tp4 label -i <<-EOF &&\n+\t\tLabel: TAG_LONG_LABEL\n+\t\tDescription:\n+\t\t   A Label first line\n+\t\t   A Label second line\n+\t\tView:\t//depot/...\n+\t\tEOF\n+\n+\t\tp4 tag -l TAG_LONG_LABEL ... &&\n+\n+\t\tp4 labels ... &&\n+\n+\t\tgit p4 clone --dest=\"$git\" //depot@all &&\n+\t\tcd \"$git\" &&\n+\t\tgit config git-p4.labelImportRegexp \".*TAG.*\" &&\n+\t\tgit p4 sync --import-labels --verbose &&\n+\n+\t\tgit tag &&\n+\t\tgit tag >taglist &&\n+\t\ttest_line_count = 3 taglist &&\n+\n+\t\tcd main &&\n+\t\tgit checkout TAG_F1_ONLY &&\n+\t\t! test -f f2 &&\n+\t\tgit checkout TAG_WITH\\$_SHELL_CHAR &&\n+\t\ttest -f f1 && test -f f2 && test -f file_with_\\$metachar &&\n+\n+\t\tgit show TAG_LONG_LABEL | grep -q \"A Label second line\"\n+\t)\n+'\n+# Test some label corner cases:\n+#\n+# - two tags on the same file; both should be available\n+# - a tag that is only on one file; this kind of tag\n+#   cannot be imported (at least not easily).\n+\n+test_expect_success 'two labels on the same changelist' '\n+\ttest_when_finished cleanup_git &&\n+\t(\n+\t\tcd \"$cli\" &&\n+\t\tmkdir -p main &&\n+\n+\t\tp4 edit main/f1 main/f2 &&\n+\t\techo \"hello world\" >main/f1 &&\n+\t\techo \"not in the tag\" >main/f2 &&\n+\t\tp4 submit -d \"main/f[12]: testing two labels\" &&\n+\n+\t\tp4 tag -l TAG_F1_1 main/... &&\n+\t\tp4 tag -l TAG_F1_2 main/... &&\n+\n+\t\tp4 labels ... &&\n+\n+\t\tgit p4 clone --dest=\"$git\" //depot@all &&\n+\t\tcd \"$git\" &&\n+\t\tgit p4 sync --import-labels &&\n+\n+\t\tgit tag | grep TAG_F1 &&\n+\t\tgit tag | grep -q TAG_F1_1 &&\n+\t\tgit tag | grep -q TAG_F1_2 &&\n+\n+\t\tcd main &&\n+\n+\t\tgit checkout TAG_F1_1 &&\n+\t\tls &&\n+\t\ttest -f f1 &&\n+\n+\t\tgit checkout TAG_F1_2 &&\n+\t\tls &&\n+\t\ttest -f f1\n+\t)\n+'\n+\n+# Export some git tags to p4\n+test_expect_success 'export git tags to p4' '\n+\ttest_when_finished cleanup_git &&\n+\tgit p4 clone --dest=\"$git\" //depot@all &&\n+\t(\n+\t\tcd \"$git\" &&\n+\t\tgit tag -m \"A tag created in git:xyzzy\" GIT_TAG_1 &&\n+\t\techo \"hello world\" >main/f10 &&\n+\t\tgit add main/f10 &&\n+\t\tgit commit -m \"Adding file for export test\" &&\n+\t\tgit config git-p4.skipSubmitEdit true &&\n+\t\tgit p4 submit &&\n+\t\tgit tag -m \"Another git tag\" GIT_TAG_2 &&\n+\t\tgit tag LIGHTWEIGHT_TAG &&\n+\t\tgit p4 rebase --import-labels --verbose &&\n+\t\tgit p4 submit --export-labels --verbose\n+\t) &&\n+\t(\n+\t\tcd \"$cli\" &&\n+\t\tp4 sync ... &&\n+\t\tp4 labels ... | grep GIT_TAG_1 &&\n+\t\tp4 labels ... | grep GIT_TAG_2 &&\n+\t\tp4 labels ... | grep LIGHTWEIGHT_TAG &&\n+\t\tp4 label -o GIT_TAG_1 | grep \"tag created in git:xyzzy\" &&\n+\t\tp4 sync ...@GIT_TAG_1 &&\n+\t\t! test -f main/f10\n+\t\tp4 sync ...@GIT_TAG_2 &&\n+\t\ttest -f main/f10\n+\t)\n+'\n+\n+# Export a tag from git where an affected file is deleted later on\n+# Need to create git tags after rebase, since only then can the\n+# git commits be mapped to p4 changelists.\n+test_expect_success 'export git tags to p4 with deletion' '\n+\ttest_when_finished cleanup_git &&\n+\tgit p4 clone --dest=\"$git\" //depot@all &&\n+\t(\n+\t\tcd \"$git\" &&\n+\t\tgit p4 sync --import-labels &&\n+\t\techo \"deleted file\" >main/deleted_file &&\n+\t\tgit add main/deleted_file &&\n+\t\tgit commit -m \"create deleted file\" &&\n+\t\tgit rm main/deleted_file &&\n+\t\techo \"new file\" >main/f11 &&\n+\t\tgit add main/f11 &&\n+\t\tgit commit -m \"delete the deleted file\" &&\n+\t\tgit config git-p4.skipSubmitEdit true &&\n+\t\tgit p4 submit &&\n+\t\tgit p4 rebase --import-labels --verbose &&\n+\t\tgit tag -m \"tag on deleted file\" GIT_TAG_ON_DELETED HEAD~1 &&\n+\t\tgit tag -m \"tag after deletion\" GIT_TAG_AFTER_DELETION HEAD &&\n+\t\tgit p4 submit --export-labels --verbose\n+\t) &&\n+\t(\n+\t\tcd \"$cli\" &&\n+\t\tp4 sync ... &&\n+\t\tp4 sync ...@GIT_TAG_ON_DELETED &&\n+\t\ttest -f main/deleted_file &&\n+\t\tp4 sync ...@GIT_TAG_AFTER_DELETION &&\n+\t\t! test -f main/deleted_file &&\n+\t\techo \"checking label contents\" &&\n+\t\tp4 label -o GIT_TAG_ON_DELETED | grep \"tag on deleted file\"\n+\t)\n+'\n+\n+# Create a tag in git that cannot be exported to p4\n+test_expect_success 'tag that cannot be exported' '\n+\ttest_when_finished cleanup_git &&\n+\tgit p4 clone --dest=\"$git\" //depot@all &&\n+\t(\n+\t\tcd \"$git\" &&\n+\t\tgit checkout -b a_branch &&\n+\t\techo \"hello\" >main/f12 &&\n+\t\tgit add main/f12 &&\n+\t\tgit commit -m \"adding f12\" &&\n+\t\tgit tag -m \"tag on a_branch\" GIT_TAG_ON_A_BRANCH &&\n+\t\tgit checkout master &&\n+\t\tgit p4 submit --export-labels\n+\t) &&\n+\t(\n+\t\tcd \"$cli\" &&\n+\t\tp4 sync ... &&\n+\t\t!(p4 labels | grep GIT_TAG_ON_A_BRANCH)\n+\t)\n+'\n+\n+test_expect_success 'kill p4d' '\n+\tkill_p4d\n+'\n+\n+test_done\n-- \n1.7.10.8.g12781\n"},{"id":"189958","messageId":"1335256403-22162-6-git-send-email-luke@diamand.org","threadId":"30321","inReplyTo":"1335256403-22162-1-git-send-email-luke@diamand.org","subject":"[PATCHv4 5/5] git p4: move verbose to base class","fromName":"Luke Diamand","fromEmail":"luke@diamand.org","sentAt":"2012-04-24T08:33:23Z","receivedAt":"2012-04-24T08:33:23Z","isPatch":false,"sender":{"key":"luke@diamand.org","avatar":"https://avatars.githubusercontent.com/u/5330967?v=4"},"body":"The verbose flag is common to all classes, or at least should be.\nMake it a member of the base Command class, rather than\nreimplementing for each class. Make option parsing mirror this.\n\nSigned-off-by: Luke Diamand <luke@diamand.org>\n---\n Documentation/git-p4.txt |   11 ++++-------\n git-p4.py                |   31 ++++++++++---------------------\n 2 files changed, 14 insertions(+), 28 deletions(-)\n\ndiff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt\nindex 01cc97b..51955a5 100644\n--- a/Documentation/git-p4.txt\n+++ b/Documentation/git-p4.txt\n@@ -158,11 +158,14 @@ OPTIONS\n \n General options\n ~~~~~~~~~~~~~~~\n-All commands except clone accept this option.\n+All commands except clone accept these options.\n \n --git-dir <dir>::\n \tSet the 'GIT_DIR' environment variable.  See linkgit:git[1].\n \n+--verbose::\n+\tProvide more progress information.\n+\n Sync options\n ~~~~~~~~~~~~\n These options can be used in the initial 'clone' as well as in\n@@ -193,9 +196,6 @@ git repository:\n --silent::\n \tDo not print any progress information.\n \n---verbose::\n-\tProvide more progress information.\n-\n --detect-labels::\n \tQuery p4 for labels associated with the depot paths, and add\n \tthem as tags in git. Limited usefulness as only imports labels\n@@ -249,9 +249,6 @@ Submit options\n ~~~~~~~~~~~~~~\n These options can be used to modify 'git p4 submit' behavior.\n \n---verbose::\n-\tProvide more progress information.\n-\n --origin <commit>::\n \tUpstream location from which commits are identified to submit to\n \tp4.  By default, this is the most recent p4 commit reachable\ndiff --git a/git-p4.py b/git-p4.py\nindex 6ce88ff..eab6959 100755\n--- a/git-p4.py\n+++ b/git-p4.py\n@@ -662,6 +662,7 @@ class Command:\n     def __init__(self):\n         self.usage = \"usage: %prog [options]\"\n         self.needsGit = True\n+        self.verbose = False\n \n class P4UserMap:\n     def __init__(self):\n@@ -727,13 +728,9 @@ class P4UserMap:\n class P4Debug(Command):\n     def __init__(self):\n         Command.__init__(self)\n-        self.options = [\n-            optparse.make_option(\"--verbose\", dest=\"verbose\", action=\"store_true\",\n-                                 default=False),\n-            ]\n+        self.options = []\n         self.description = \"A tool to debug the output of p4 -G.\"\n         self.needsGit = False\n-        self.verbose = False\n \n     def run(self, args):\n         j = 0\n@@ -747,11 +744,9 @@ class P4RollBack(Command):\n     def __init__(self):\n         Command.__init__(self)\n         self.options = [\n-            optparse.make_option(\"--verbose\", dest=\"verbose\", action=\"store_true\"),\n             optparse.make_option(\"--local\", dest=\"rollbackLocalBranches\", action=\"store_true\")\n         ]\n         self.description = \"A tool to debug the multi-branch import. Don't use :)\"\n-        self.verbose = False\n         self.rollbackLocalBranches = False\n \n     def run(self, args):\n@@ -809,7 +804,6 @@ class P4Submit(Command, P4UserMap):\n         Command.__init__(self)\n         P4UserMap.__init__(self)\n         self.options = [\n-                optparse.make_option(\"--verbose\", dest=\"verbose\", action=\"store_true\"),\n                 optparse.make_option(\"--origin\", dest=\"origin\"),\n                 optparse.make_option(\"-M\", dest=\"detectRenames\", action=\"store_true\"),\n                 # preserve the user, requires relevant p4 permissions\n@@ -821,7 +815,6 @@ class P4Submit(Command, P4UserMap):\n         self.interactive = True\n         self.origin = \"\"\n         self.detectRenames = False\n-        self.verbose = False\n         self.preserveUser = gitConfig(\"git-p4.preserveUser\").lower() == \"true\"\n         self.isWindows = (platform.system() == \"Windows\")\n         self.exportLabels = False\n@@ -1644,7 +1637,6 @@ class P4Sync(Command, P4UserMap):\n                 optparse.make_option(\"--silent\", dest=\"silent\", action=\"store_true\"),\n                 optparse.make_option(\"--detect-labels\", dest=\"detectLabels\", action=\"store_true\"),\n                 optparse.make_option(\"--import-labels\", dest=\"importLabels\", action=\"store_true\"),\n-                optparse.make_option(\"--verbose\", dest=\"verbose\", action=\"store_true\"),\n                 optparse.make_option(\"--import-local\", dest=\"importIntoRemotes\", action=\"store_false\",\n                                      help=\"Import into refs/heads/ , not refs/remotes\"),\n                 optparse.make_option(\"--max-changes\", dest=\"maxChanges\"),\n@@ -1671,7 +1663,6 @@ class P4Sync(Command, P4UserMap):\n         self.importLabels = False\n         self.changesFile = \"\"\n         self.syncWithOrigin = True\n-        self.verbose = False\n         self.importIntoRemotes = True\n         self.maxChanges = \"\"\n         self.isWindows = (platform.system() == \"Windows\")\n@@ -2712,9 +2703,7 @@ class P4Rebase(Command):\n         Command.__init__(self)\n         self.options = [\n                 optparse.make_option(\"--import-labels\", dest=\"importLabels\", action=\"store_true\"),\n-                optparse.make_option(\"--verbose\", dest=\"verbose\", action=\"store_true\"),\n         ]\n-        self.verbose = False\n         self.importLabels = False\n         self.description = (\"Fetches the latest revision from perforce and \"\n                             + \"rebases the current work (branch) against it\")\n@@ -2911,16 +2900,16 @@ def main():\n \n     args = sys.argv[2:]\n \n-    if len(options) > 0:\n-        if cmd.needsGit:\n-            options.append(optparse.make_option(\"--git-dir\", dest=\"gitdir\"))\n+    options.append(optparse.make_option(\"--verbose\", dest=\"verbose\", action=\"store_true\"))\n+    if cmd.needsGit:\n+        options.append(optparse.make_option(\"--git-dir\", dest=\"gitdir\"))\n \n-        parser = optparse.OptionParser(cmd.usage.replace(\"%prog\", \"%prog \" + cmdName),\n-                                       options,\n-                                       description = cmd.description,\n-                                       formatter = HelpFormatter())\n+    parser = optparse.OptionParser(cmd.usage.replace(\"%prog\", \"%prog \" + cmdName),\n+                                   options,\n+                                   description = cmd.description,\n+                                   formatter = HelpFormatter())\n \n-        (cmd, args) = parser.parse_args(sys.argv[2:], cmd);\n+    (cmd, args) = parser.parse_args(sys.argv[2:], cmd);\n     global verbose\n     verbose = cmd.verbose\n     if cmd.needsGit:\n-- \n1.7.10.8.g12781\n"}]}