Git pre-commit or update hook to stop commit with branch names matching case insensitive

Is there a way to write a pre-commit git binding to stop commits with the same name, with the only difference being upper and lower case.

eg,
branch name 1: firstBranch
branch name 2: FirstBrancH
branch name 3: firsTbranch

but branch name: firstbranchname must be resolved.

if the commit is performed at time T for the firstBranch branch name, then in T + n, Commit with the branch name "FirstBrancH" or any combination, git pre-hook does not allow the commit. This should be a server hook, as client hooks can be easily pypassed.

so my thoughts are:

so I get the $ NAME of the branch that is assigned, and then compare it with ALL branches that ignore CASE, and DENY it with the message if the match passes.

I have a pre-receive hook setting on the gitlab server:

#!/bin/bash check_dup_branch=`git branch -a | sed 's; remotes/origin/;;g' | tr '[:upper:]' '[:lower:]' | uniq -d` if [ check_dup_branch ] then echo "Duplicate CaseInsensitive Branch Name Detected" exit 1 fi exit 0 

according to instructions:

  • Select a project that requires a custom git hook.

  • On the GitLab server, navigate to the project repository directory. For installation from the source, the path is usually / home / git / repositories //. Git. To install Omnibus, the path is usually / var / opt / gitlab / git -data / repositories //. Git.

  • Create a new directory in this place called custom_hooks.

  • Inside the new custom_hooks directory, create a file with a name that matches the hook type. For pre-reception, the file name must be pre-obtained without the extension.

  • Make the hook executable and make sure it belongs to git.

  • Write code to make the hook git function as expected. Hooks can be in any language. Make sure the "shebang" at the top correctly reflects the type of language. For example, if the script is in Ruby, shebang will probably be #! / Usr / bin / env ruby.

But it does not work as expected.

if I click aaa when AAA is already in gitlab, it gives me an error:

 remote: Duplicate CaseInsensitive Branch Name Detected 

but it also gives me the same β€œduplicate” message when I try to push the bbb branch

I expect it to not commit if the branch name is duplicated), ignoring case)

 After a bit more study on git hooks: 

ref: If you want to accept or reject branches in each case, you need to use the update hook instead.

when hook update:

 #!/usr/bin/python import sys print "Testing pre-receive Hook in Python" branch = sys.argv[1] print "Branch '%s' pushing" %(branch) sys.exit(0) 

git push origin AAA

 Total 0 (delta 0), reused 0 (delta 0) remote: Testing pre-receive Hook in Python remote: Branch 'refs/heads/AAA' pushing 
  • [new branch] AAA β†’ AAA

now we need to compare how grep -i, git branch -a and make uniq -d with aaa, after the lower ALL casing

and then comparing, and IF there is MATCH, call sys.exit (1)

DO NOT allow click

python update hook:

 #!/usr/bin/python import sys import subprocess #print "Testing pre-receive Hook" branch = sys.argv[1] old_commit = sys.argv[2] new_commit = sys.argv[3] #print "Moving '%s' from %s to %s" % (branch, old_commit, new_commit) #print "Branch '%s' pushing" %(branch) #print "old_commit '%s' pushing" %(old_commit) #print "new_commit '%s' pushing" %(new_commit) def git(*args): return subprocess.check_call(['git'] + list(args)) if __name__ == "__main__": #git("status") #git("for-each-ref" , "refs/heads/" , "--format='%(refname:short)'") git("for-each-ref" , "--format='%(refname:short)'") sys.exit(0) 

further improvement in python update cache:

 #!/usr/bin/python import sys import subprocess #print "Testing pre-receive Hook" branch = sys.argv[1] old_commit = sys.argv[2] new_commit = sys.argv[3] # order is important, for update hook: refname oldsha1 newsha1 print "Moving '%s' from %s to %s" % (branch, old_commit, new_commit) print "Branch '%s' " %(branch) print "old_commit '%s' " %(old_commit) print "new_commit '%s' " %(new_commit) def git(*args): return subprocess.check_call(['git'] + list(args)) #if %(branch).lower() in []array of results from the git for-each-ref #sys.exit(1) def get_name(target): p = subprocess.Popen(['git', 'for-each-ref', 'refs/heads/'], stdout=subprocess.PIPE) for line in p.stdout: sha1, kind, name = line.split() if sha1 != target: continue return name return None if __name__ == "__main__": #git("status") #git("for-each-ref" , "refs/heads/" , "--format='%(refname:short)'") #cmd = git("for-each-ref" , "--format='%(refname:short)'") cmd = git("for-each-ref" , "--format='%(refname:short)'") #print cmd #print get_name(branch) #print get_name(old_commit) print get_name(new_commit) sys.exit(0) 

therefore, the rejection case, of course, compares the current% (branch) or% (refname: short) and compares it with all existing names in the IgnoreCase method, and if it is found (1 or many), then run the sys.exit (1) command with message "Duplicate branch name"

but currently i am getting:

 remote: Moving 'refs/heads/IIII' from 0000000000000000000000000000000000000000 to 4453eb046fe11c8628729d74c3bec1dd2018512e remote: Branch 'refs/heads/IIII' remote: old_commit '0000000000000000000000000000000000000000' remote: new_commit '4453eb046fe11c8628729d74c3bec1dd2018512e' remote: refs/heads/10B 

and somehow remote: refs / heads / 10B remains static. so I'm not sure how I can convert the result:

 cmd = git("for-each-ref" , "--format='%(refname:short)'") 

to a list or array, and then perform a string comparison between each element and remote: Branch 'refs / heads / IIII'

0
source share
1 answer

Of course, there is a way (or several ways). Now that you have added your editing, I will make a few notes:

  • On the server receiving the push, there are two hooks that receive the necessary information and which can reject the push. These are the pre-receive and update hooks.

  • The pre-receive hook receives at its standard input a series of lines of the form: oldsha1 newsha1 refname . He must read all these lines, process them and make a decision: accept (exit 0) or reject (exit non-zero). Exiting a nonzero value will cause the receiving server to reject all push.

  • Assuming that the pre-receive hook, if any, has not yet rejected all push: the update hook receives refname oldsha1 newsha1 as arguments (note the different order, and the fact that these are arguments). It is called once for each link to be updated, i.e. If the pre-receive hook scans five lines, the update hook is called five times. The update hook should check its arguments and decide whether to accept (output 0) or reject (print non-zero) this particular link update.

  • In all cases, refname is the full name of the link. This means that for a branch, it starts with refs/heads/ ; for a tag, it starts with refs/tags/ ; for a note (see git notes ), it starts with refs/notes ; and so on. Similarly, at most one of oldsha1 and newsha1 can be all-zeros to indicate that a link is created (old - all 0 s) or deleted (new - all 0 s).

If you want to reject some creation cases, but allow updates to be rejected for creation, check the oldsha1 values ​​as well as the ref names. If you want to reject updates, just check ref-names.

To get a list of all existing link names, use the git plumbing git for-each-ref command. To limit your output to branch names only, you can give it the prefix refs/heads . Read its documentation as you have many buttons that you can rotate.


Change Python re-code: if you are going to do this in Python, you can take advantage of Python's relative intelligence. You seem to be on the right track. Here, how I would encode it (it can be slightly redesigned, sometimes I try to sometimes handle possible future problems):

 #!/usr/bin/python """ Update hook that rejects a branch creation (but does not reject normal update nor deletion) of a branch whose name matches some other, existing branch. """ # NB: we can't actually get git to supply additional # arguments but this lets us test things locally, in # a repository. import argparse import sys import subprocess NULL_SHA1 = b'0' * 40 # Because we're using git we store and match the ref # name as a byte string (this matters for Python3, but not # for Python2). PREFIX_TO_TYPE = { b'refs/heads/': 'branch', b'refs/tags/': 'tag', b'refs/remotes/': 'remote-branch', } def get_reftype(refname): """ Convert full byte-string reference name to type; return the type (regular Python string) and the short name (binary string). Type may be 'unknown' in which case the short name is the full name. """ for key in PREFIX_TO_TYPE.keys(): if refname.startswith(key): return PREFIX_TO_TYPE[key], refname[len(key):] return 'unknown', refname class RefUpdate(object): """ A reference update has a reference name and two hashes, old and new. """ def __init__(self, refname, old, new): self.refname = refname self.reftype, self._shortref = get_reftype(refname) self.old = old self.new = new def __str__(self): return '{0}({1} [{2} {3}], {4}, {5})'.format(self.__class__.__name__, self.refname.decode('ascii'), self.reftype, self.shortref.decode('ascii'), self.old.decode('ascii'), self.new.decode('ascii')) @property def shortref(self): "get the short version of the ref (read-only, property fn)" return self._shortref @property def is_branch(self): return self.reftype == 'branch' @property def is_create(self): return self.old == NULL_SHA1 def get_existing_branches(): """ Use git for-each-ref to find existing ref names. Note that we only care about branches here, and we can take them in their short forms. Return a list of all branch names. Note that these are binary strings. """ proc = subprocess.Popen(['git', 'for-each-ref', '--format=%(refname:short)', 'refs/heads/'], stdout=subprocess.PIPE) result = proc.stdout.read().splitlines() status = proc.wait() if status != 0: sys.exit('help! git for-each-ref failed: exit {0}'.format(status)) return result def update_hook(): parser = argparse.ArgumentParser(description= 'git update hook that rejects branch create' ' for case-insensitive name collision') parser.add_argument('-v', '--verbose', action='store_true') parser.add_argument('-d', '--debug', action='store_true') parser.add_argument('ref', help= 'full reference name for update (eg, refs/heads/branch)') parser.add_argument('old_hash', help='previous hash of ref') parser.add_argument('new_hash', help='proposed new hash of ref') args = parser.parse_args() update = RefUpdate(args.ref.encode('utf-8'), args.old_hash.encode('utf-8'), args.new_hash.encode('utf-8')) if args.debug: args.verbose = True if args.verbose: print('checking update {0}'.format(update)) # if not a branch, allow if not update.is_branch: if args.debug: print('not a branch; allowing') sys.exit(0) # if not a creation, allow if not update.is_create: if args.debug: print('not a create; allowing') sys.exit(0) # check for name collision - get existing branch names if args.debug: print('branch creation! checking existing names...') names = get_existing_branches() for name in names: if args.debug: print('check vs {0} = {1}'.format(name.decode('ascii'), name.lower().decode('ascii'))) if update.shortref.lower() == name.lower(): sys.exit('Create branch {0} denied: collides with' ' existing branch {1}'.format(update.shortref.decode('ascii'), name.decode('ascii'))) # whew, made it, allow if args.verbose: print('all tests passed, allowing') return 0 if __name__ == "__main__": try: sys.exit(update_hook()) except KeyboardInterrupt: sys.exit('\nInterrupted') 
+2
source

Source: https://habr.com/ru/post/1261312/


All Articles