Re: [PATCH] git-p4: add option to preserve user names

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



luke@xxxxxxxxxxx wrote on Thu, 21 Apr 2011 20:50 +0100:
> Patches from git passed into p4 end up with the committer
> being identified as the person who ran git-p4.
> 
> With "submit --preserve-user", git-p4 modifies the p4
> changelist (after it has been submitted), setting the
> p4 author field.
> 
> The submitter is required to have sufficient p4 permissions
> or git-p4 refuses to proceed. If the git author is not
> known to p4, the submit will be abandoned unless
> git-p4.allowMissingP4Users is true.
> 
> Signed-off-by: Luke Diamand <luke@xxxxxxxxxxx>

I like this patch.  Quite impressed with the care you put into
refactoring, handling all the error cases, and documentation too.

Acked-by: Pete Wyckoff <pw@xxxxxxxx>

Maybe wait for some more comments, then send it to Junio in a week.

		-- Pete


> ---
>  contrib/fast-import/git-p4     |  179 +++++++++++++++++++++++++++++++---------
>  contrib/fast-import/git-p4.txt |   29 +++++++
>  t/t9800-git-p4.sh              |   84 +++++++++++++++++++
>  3 files changed, 254 insertions(+), 38 deletions(-)
> 
> diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
> index 78e5b3a..36e3d87 100755
> --- a/contrib/fast-import/git-p4
> +++ b/contrib/fast-import/git-p4
> @@ -474,6 +474,47 @@ class Command:
>          self.usage = "usage: %prog [options]"
>          self.needsGit = True
>  
> +class P4UserMap:
> +    def __init__(self):
> +        self.userMapFromPerforceServer = False
> +
> +    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 = {}
> +        self.emails = {}
> +
> +        for output in p4CmdList("users"):
> +            if not output.has_key("User"):
> +                continue
> +            self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
> +            self.emails[output["Email"]] = output["User"]
> +
> +
> +        s = ''
> +        for (key, val) in self.users.items():
> +            s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
> +
> +        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()
> +
>  class P4Debug(Command):
>      def __init__(self):
>          Command.__init__(self)
> @@ -554,13 +595,16 @@ class P4RollBack(Command):
>  
>          return True
>  
> -class P4Submit(Command):
> +class P4Submit(Command, P4UserMap):
>      def __init__(self):
>          Command.__init__(self)
> +        P4UserMap.__init__(self)
>          self.options = [
>                  optparse.make_option("--verbose", dest="verbose", action="store_true"),
>                  optparse.make_option("--origin", dest="origin"),
>                  optparse.make_option("-M", dest="detectRenames", action="store_true"),
> +                # preserve the user, requires relevant p4 permissions
> +                optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"),
>          ]
>          self.description = "Submit changes from git to the perforce depot."
>          self.usage += " [name of git branch to submit into perforce depot]"
> @@ -568,6 +612,7 @@ class P4Submit(Command):
>          self.origin = ""
>          self.detectRenames = False
>          self.verbose = False
> +        self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
>          self.isWindows = (platform.system() == "Windows")
>  
>      def check(self):
> @@ -602,6 +647,75 @@ class P4Submit(Command):
>  
>          return result
>  
> +    def p4UserForCommit(self,id):
> +        # Return the tuple (perforce user,git email) for a given git commit id
> +        self.getUserMapFromPerforceServer()
> +        gitEmail = read_pipe("git log --max-count=1 --format='%%ae' %s" % id)
> +        gitEmail = gitEmail.strip()
> +        if not self.emails.has_key(gitEmail):
> +            return (None,gitEmail)
> +        else:
> +            return (self.emails[gitEmail],gitEmail)
> +
> +    def checkValidP4Users(self,commits):
> +        # check if any git authors cannot be mapped to p4 users
> +        for id in commits:
> +            (user,email) = self.p4UserForCommit(id)
> +            if not user:
> +                msg = "Cannot find p4 user for email %s in commit %s." % (email, id)
> +                if gitConfig('git-p4.allowMissingP4Users').lower() == "true":
> +                    print "%s" % msg
> +                else:
> +                    die("Error: %s\nSet git-p4.allowMissingP4Users to true to allow this." % msg)
> +
> +    def lastP4Changelist(self):
> +        # Get back the last changelist number submitted in this client spec. This
> +        # then gets used to patch up the username in the change. If the same
> +        # client spec is being used by multiple processes then this might go
> +        # wrong.
> +        results = p4CmdList("client -o")        # find the current client
> +        client = None
> +        for r in results:
> +            if r.has_key('Client'):
> +                client = r['Client']
> +                break
> +        if not client:
> +            die("could not get client spec")
> +        results = p4CmdList("changes -c %s -m 1" % client)
> +        for r in results:
> +            if r.has_key('change'):
> +                return r['change']
> +        die("Could not get changelist number for last submit - cannot patch up user details")
> +
> +    def modifyChangelistUser(self, changelist, newUser):
> +        # fixup the user field of a changelist after it has been submitted.
> +        changes = p4CmdList("change -o %s" % changelist)
> +        for c in changes:
> +            if c.has_key('User'):
> +                c['User'] = newUser
> +        input = marshal.dumps(changes[0])
> +        result = p4CmdList("change -f -i", stdin=input)
> +        for r in result:
> +            if r.has_key('code'):
> +                if r['code'] == 'error':
> +                    die("Could not modify user field of changelist %s to %s:%s" % (changelist, newUser, r['data']))
> +            if r.has_key('data'):
> +                print("Updated user field for changelist %s to %s" % (changelist, newUser))
> +                return
> +        die("Could not modify user field of changelist %s to %s" % (changelist, newUser))
> +
> +    def canChangeChangelists(self):
> +        # check to see if we have p4 admin or super-user permissions, either of
> +        # which are required to modify changelists.
> +        results = p4CmdList("-G protects %s" % self.depotPath)
> +        for r in results:
> +            if r.has_key('perm'):
> +                if r['perm'] == 'admin':
> +                    return 1
> +                if r['perm'] == 'super':
> +                    return 1
> +        return 0
> +
>      def prepareSubmitTemplate(self):
>          # remove lines in the Files section that show changes to files outside the depot path we're committing into
>          template = ""
> @@ -631,6 +745,9 @@ class P4Submit(Command):
>      def applyCommit(self, id):
>          print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
>  
> +        if self.preserveUser:
> +            (p4User, gitEmail) = self.p4UserForCommit(id)
> +
>          if not self.detectRenames:
>              # If not explicitly set check the config variable
>              self.detectRenames = gitConfig("git-p4.detectRenames").lower() == "true"
> @@ -781,8 +898,13 @@ class P4Submit(Command):
>                  editor = read_pipe("git var GIT_EDITOR").strip()
>              system(editor + " " + fileName)
>  
> +            if gitConfig("git-p4.skipSubmitEditCheck") == "true":
> +                checkModTime = False
> +            else:
> +                checkModTime = True
> +
>              response = "y"
> -            if os.stat(fileName).st_mtime <= mtime:
> +            if checkModTime and (os.stat(fileName).st_mtime <= mtime):
>                  response = "x"
>                  while response != "y" and response != "n":
>                      response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
> @@ -795,6 +917,14 @@ class P4Submit(Command):
>                  if self.isWindows:
>                      submitTemplate = submitTemplate.replace("\r\n", "\n")
>                  p4_write_pipe("submit -i", submitTemplate)
> +
> +                if self.preserveUser:
> +                    if p4User:
> +                        # Get last changelist number. Cannot easily get it from
> +                        # the submit command output as the output is unmarshalled.
> +                        changelist = self.lastP4Changelist()
> +                        self.modifyChangelistUser(changelist, p4User)
> +
>              else:
>                  for f in editedFiles:
>                      p4_system("revert \"%s\"" % f);
> @@ -831,6 +961,10 @@ class P4Submit(Command):
>          if len(self.origin) == 0:
>              self.origin = upstream
>  
> +        if self.preserveUser:
> +            if not self.canChangeChangelists():
> +                die("Cannot preserve user names without p4 super-user or admin permissions")
> +
>          if self.verbose:
>              print "Origin branch is " + self.origin
>  
> @@ -858,6 +992,9 @@ class P4Submit(Command):
>              commits.append(line.strip())
>          commits.reverse()
>  
> +        if self.preserveUser:
> +            self.checkValidP4Users(commits)
> +
>          while len(commits) > 0:
>              commit = commits[0]
>              commits = commits[1:]
> @@ -877,11 +1014,12 @@ class P4Submit(Command):
>  
>          return True
>  
> -class P4Sync(Command):
> +class P4Sync(Command, P4UserMap):
>      delete_actions = ( "delete", "move/delete", "purge" )
>  
>      def __init__(self):
>          Command.__init__(self)
> +        P4UserMap.__init__(self)
>          self.options = [
>                  optparse.make_option("--branch", dest="branch"),
>                  optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
> @@ -1236,41 +1374,6 @@ class P4Sync(Command):
>                      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.expandtabs(1), val.expandtabs(1))
> -
> -        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 = {}
>  
> diff --git a/contrib/fast-import/git-p4.txt b/contrib/fast-import/git-p4.txt
> index e09da44..b6986f0 100644
> --- a/contrib/fast-import/git-p4.txt
> +++ b/contrib/fast-import/git-p4.txt
> @@ -110,6 +110,12 @@ is not your current git branch you can also pass that as an argument:
>  
>  You can override the reference branch with the --origin=mysourcebranch option.
>  
> +The Perforce changelists will be created with the user who ran git-p4. If you
> +use --preserve-user then git-p4 will attempt to create Perforce changelists
> +with the Perforce user corresponding to the git commit author. You need to
> +have sufficient permissions within Perforce, and the git users need to have
> +Perforce accounts. Permissions can be granted using 'p4 protect'.
> +
>  If a submit fails you may have to "p4 resolve" and submit manually. You can
>  continue importing the remaining changes with
>  
> @@ -196,6 +202,29 @@ able to find the relevant client.  This client spec will be used to
>  both filter the files cloned by git and set the directory layout as
>  specified in the client (this implies --keep-path style semantics).
>  
> +git-p4.skipSubmitModTimeCheck
> +
> +  git config [--global] git-p4.skipSubmitModTimeCheck false
> +
> +If true, submit will not check if the p4 change template has been modified.
> +
> +git-p4.preserveUser
> +
> +  git config [--global] git-p4.preserveUser false
> +
> +If true, attempt to preserve user names by modifying the p4 changelists. See
> +the "--preserve-user" submit option.
> +
> +git-p4.allowMissingPerforceUsers
> +
> +  git config [--global] git-p4.allowMissingP4Users false
> +
> +If git-p4 is setting the perforce user for a commit (--preserve-user) then
> +if there is no perforce user corresponding to the git author, git-p4 will
> +stop. With allowMissingPerforceUsers set to true, git-p4 will use the
> +current user (i.e. the behavior without --preserve-user) and carry on with
> +the perforce commit.
> +
>  Implementation Details...
>  =========================
>  
> diff --git a/t/t9800-git-p4.sh b/t/t9800-git-p4.sh
> index a523473..fd3204b 100755
> --- a/t/t9800-git-p4.sh
> +++ b/t/t9800-git-p4.sh
> @@ -12,6 +12,8 @@ test_description='git-p4 tests'
>  GITP4=$GIT_BUILD_DIR/contrib/fast-import/git-p4
>  P4DPORT=10669
>  
> +export P4PORT=localhost:$P4DPORT
> +
>  db="$TRASH_DIRECTORY/db"
>  cli="$TRASH_DIRECTORY/cli"
>  git="$TRASH_DIRECTORY/git"
> @@ -129,6 +131,88 @@ test_expect_success 'clone bare' '
>  	rm -rf "$git" && mkdir "$git"
>  '
>  
> +p4_add_user() {
> +    name=$1
> +    fullname=$2
> +    p4 user -f -i <<EOF &&
> +User: $name
> +Email: $name@localhost
> +FullName: $fullname
> +EOF
> +    p4 passwd -P secret $name
> +}
> +
> +p4_grant_admin() {
> +    name=$1
> +    p4 protect -o |\
> +        awk "{print}END{print \"    admin user $name * //depot/...\"}" |\
> +        p4 protect -i
> +}
> +
> +p4_check_commit_author() {
> +    file=$1
> +    user=$2
> +    if p4 changes -m 1 //depot/$file | grep $user > /dev/null ; then
> +        return 0
> +    else
> +        echo "file $file not modified by user $user" 1>&2
> +        return 1
> +    fi
> +}
> +
> +# Test username support, submitting as user 'alice'
> +test_expect_success 'preserve users' '
> +	p4_add_user alice Alice &&
> +	p4_add_user bob Bob &&
> +	p4_grant_admin alice &&
> +	"$GITP4" clone --dest="$git" //depot &&
> +	cd "$git" &&
> +	echo "username: a change by alice" >> file1 &&
> +	echo "username: a change by bob" >> file2 &&
> +	git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 &&
> +	git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 &&
> +	git config git-p4.skipSubmitEditCheck true &&
> +	P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
> +	p4_check_commit_author file1 alice &&
> +	p4_check_commit_author file2 bob &&
> +	cd "$TRASH_DIRECTORY" &&
> +	rm -rf "$git" && mkdir "$git"
> +'
> +
> +# Test username support, submitting as bob, who lacks admin rights. Should
> +# not submit change to p4 (git diff should show deltas).
> +test_expect_success 'refuse to preserve users without perms' '
> +	"$GITP4" clone --dest="$git" //depot &&
> +	cd "$git" &&
> +	echo "username-noperms: a change by alice" >> file1 &&
> +	git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
> +	! P4EDITOR=touch P4USER=bob P4PASSWD=secret "$GITP4" commit --preserve-user &&
> +	! git diff --exit-code HEAD..p4/master > /dev/null &&
> +	cd "$TRASH_DIRECTORY" &&
> +	rm -rf "$git" && mkdir "$git"
> +'
> +
> +# What happens with unknown author? Without allowMissingP4Users it should fail.
> +test_expect_success 'preserve user where author is unknown to p4' '
> +	"$GITP4" clone --dest="$git" //depot &&
> +	cd "$git" &&
> +	git config git-p4.skipSubmitEditCheck true
> +	echo "username-bob: a change by bob" >> file1 &&
> +	git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
> +	echo "username-unknown: a change by charlie" >> file1 &&
> +	git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
> +	! P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
> +	! git diff --exit-code HEAD..p4/master > /dev/null &&
> +	echo "$0: repeat with allowMissingP4Users enabled" &&
> +	git config git-p4.allowMissingP4Users true &&
> +	git config git-p4.preserveUser true &&
> +	P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit &&
> +	git diff --exit-code HEAD..p4/master > /dev/null &&
> +	p4_check_commit_author file1 alice &&
> +	cd "$TRASH_DIRECTORY" &&
> +	rm -rf "$git" && mkdir "$git"
> +'
> +
>  test_expect_success 'shutdown' '
>  	pid=`pgrep -f p4d` &&
>  	test -n "$pid" &&
> -- 
> 1.7.1
> 
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]