def checkout(self): shallow = self.conf.get("shallow", False) if self.ref_type == 'commit' and shallow: # We cannot straight clone a shallow repo using a commit hash (-b doesn't support it) # do the dance described at https://stackoverflow.com/a/43136160/214671 os.mkdir(self.directory) with cd(self.directory): fork(['git', 'init']) fork(['git', 'remote', 'add', 'origin', self.url.geturl()]) fork([ 'git', 'fetch', '--depth', '1', 'origin', self.noremote_ref() ]) fork(['git', 'checkout', self.ref, '--']) else: # Regular case extra_opts = [] if shallow: extra_opts += ["--depth", "1"] # Needed essentially for the shallow case, as for full clones the # git clone -n + git checkout would suffice if shallow and self.ref_type != 'commit' and self.ref != 'origin/HEAD': extra_opts += ['-b', self.noremote_ref()] fork(['git', 'clone', '-n'] + extra_opts + ['--', self.url.geturl(), self.directory]) with cd(self.directory): opts = [self.ref] # If it's a branch, create a remote-tracking one if self.ref_type == 'branch' and not shallow: # Find out a sensible local branch name (needed for origin/HEAD) local_branch = self.symbolic_full_name(self.ref).split( '/origin/', 1)[1] opts = [local_branch] fork(['git', 'checkout'] + opts + ['--'])
def update(self, clean=False): if not exists(self.directory): self.checkout() elif not exists(self.directory + "/.svn"): not_a_project(self.directory, "Subversion") else: with cd(self.directory): if self.has_local_edit(): if clean: fork(['svn', 'revert', '-R', '.']) fork(['svn', 'cleanup', '--remove-unversioned', '.']) else: logger.warning( "Directory '%s' contains local modifications" % self.directory) # svn switch _would be ok_ even just to perform an update, but, # unlike svn up, it touches the timestamp of all the files, # forcing full rebuilds; so, if we are already on the correct # url just use svn up # Notice that, unlike other svn commands, -r in svn up works as # a peg revision (the @ syntax), so it takes the URL of the # current working copy and looks it up in the repository _as it # was at the requested revision_ (or HEAD if none is specified) target_base, target_rev = (self.url.geturl().split('@') + [''])[:2] if target_base == self.url_from_checkout(include_commit=False): fork(['svn', 'up'] + (["-r" + target_rev] if target_rev else [])) else: fork(['svn', 'switch', self.url.geturl()])
def mirror(self, dst_dir): source_dir = self.directory def mkdir_p(path): if path.strip() != '' and not os.path.exists(path): os.makedirs(path) env = os.environ.copy() env['LC_MESSAGES'] = 'C' def tracked_files(): p = Popen(['git', 'ls-tree', '-r', '--name-only', 'HEAD'], stdout=PIPE, env=env) out = p.communicate()[0] if p.returncode != 0 or not out.strip(): return None return [e.strip() for e in out.splitlines() if os.path.exists(e)] def cp(src, dst): r, f = os.path.split(dst) mkdir_p(r) shutil.copy2(src, dst) with cd(source_dir): for t in tracked_files(): cp(t, os.path.join(dst_dir, t.decode()))
def update(self): if not exists(self.directory): self.checkout() elif self.has_local_edit(): logger.warning("Directory '%s' contains local modifications" % self.directory) else: with cd(self.directory): fork(['svn', 'switch', self.url.geturl()])
def url_from_directory(directory, include_commit = True): with cd(directory): origin = check_output(['git', 'remote', 'get-url', 'origin'], universal_newlines=True)[:-1] commit = check_output(['git', 'log', '-1', '--format=%H'], universal_newlines=True)[:-1] ret = 'git+%s' % (origin,) if include_commit: ret += '#commit=%s' % (commit,) return ret
def checkout(self): fork(['git', 'clone', '-n', '--', self.url.geturl(), self.directory]) with cd(self.directory): try: fork(['git', 'checkout', self.ref]) except CalledProcessError: fork(['git', 'branch', '--remote']) raise
def update(self): if not exists(self.directory): self.checkout() elif self.has_local_edit(): logger.warning("Directory '%s' contains local modifications" % self.directory) else: with cd(self.directory): fork(['git', 'fetch']) fork(['git', 'checkout', self.ref, '--'])
def actualUpdate(): with cd(self.directory): try: current_origin = log_check_output(['git', 'config', '--get', 'remote.origin.url']).strip().decode('utf-8') except CalledProcessError: current_origin = None if current_origin != self.url.geturl(): # For now we just throw a fit; it shouldn't happen often, # and in this case there's no "right" answer - the repo may # have just moved, or we may be dealing with a completely # unrelated repo. # In future, it would be nice to be a bit more interactive raise QuarkError(""" Directory '%s' is a git repository, but its remote 'origin' (%r) does not match what we expect (%r). Please either remove the local clone, or fix its remote.""" % (self.directory, current_origin, self.url.geturl())) if self.conf.get("shallow", False): # Fetch just the commit we need fork(['git', 'fetch', '--depth', '1', 'origin', self.noremote_ref()]) # Notice that we need FETCH_HEAD, as the shallow clone does not recognize # origin/HEAD & co. fork(['git', 'checkout', 'FETCH_HEAD', '--']) else: fork(['git', 'fetch']) # If we want to go on a branch, try to find a local branch that tracks it # and use it (possibly with a fast-forward) if self.ref_type == 'branch': # Resolve the remote ref remote_fullref = self.symbolic_full_name(self.ref) # Get a sensible local branch name to try local_ref = remote_fullref.split('/origin/', 1)[1] # Check if it is actually tracking our target try: local_fulltrackref = self.symbolic_full_name(local_ref + "@{u}") except CalledProcessError: # It's fine if it fails - we may not have a local-tracking branch, # so git checkout will do the right thing here local_fulltrackref = remote_fullref if remote_fullref == local_fulltrackref: try: # Checkout and fast-forward fork(['git', 'checkout', local_ref, '--']) fork(['git', 'merge', '--ff-only', self.ref, '--']) # Final sanity check if log_check_output(['git', 'rev-parse', self.ref, '--']) != log_check_output(['git', 'rev-parse', local_ref, '--']): logger.warning("Warning: your local branch is ahead of required remote branch!") return except CalledProcessError: logger.warning("Couldn't fast-forward local branch, fallback to detached head mode...") # General case: plain checkout of the origin ref (going in detached HEAD) fork(['git', 'checkout', self.ref, '--'])
def check_origin(self): with cd(self.directory): if check_output(['git', 'config', '--get', 'remote.origin.url']) != self.url: if not self.has_local_edit(): logger.warning("%s is not a clone of %s " "but it hasn't local modifications, " "removing it..", self.directory, self.url.geturl()) rmtree(self.directory) self.checkout() else: raise ValueError( "'%s' is not a clone of '%s' and has local" " modifications, I don't know what to do with it..." % self.directory, self.url.geturl())
def symbolic_full_name(self, ref): with cd(self.directory): return log_check_output( ['git', 'rev-parse', '--symbolic-full-name', ref, '--']).split(b'\n')[0].strip().decode('utf-8')
def has_local_edit(self): with cd(self.directory): return log_check_output(['git', 'status', '--porcelain']) != b""
def clean_all(self): with cd(self.directory): fork(['git', 'clean', '-fd'])
def pop(self): with cd(self.directory): fork(['git', 'stash', 'pop'])
def stash(self): with cd(self.directory): fork(['git', 'stash', '--all'])
def actualUpdate(): with cd(self.directory): try: current_origin = log_check_output( ['git', 'config', '--get', 'remote.origin.url']).strip().decode('utf-8') except CalledProcessError: current_origin = None if current_origin != self.url.geturl(): # For now we just throw a fit; it shouldn't happen often, # and in this case there's no "right" answer - the repo may # have just moved, or we may be dealing with a completely # unrelated repo. # In future, it would be nice to be a bit more interactive raise QuarkError( """ Directory '%s' is a git repository, but its remote 'origin' (%r) does not match what we expect (%r). Please either remove the local clone, or fix its remote.""" % (self.directory, current_origin, self.url.geturl())) if self.conf.get("shallow", False): # git fetch with shallow clones isn't very smart, and # re-fetches stuff that we already have; try to avoid this # FIXME: the current approach means that shallow clones # will be in detached HEAD state to a commit pretty much # always. # Probably this is not a big problem because shallow repos # in Quark aren't meant to be used for actual repo work, # and the previous implementation always had FETCH_HEAD # checked out; however, in future it would be nice to have # a cleaner solution. if self.ref_type == 'commit': # Easy case: we already know exactly the commit we need remote_commit = self.ref else: # Ask the remote what we are expected to have here remote_commit = log_check_output([ 'git', 'ls-remote', 'origin', self.noremote_ref() ]).split(b'\t')[0].strip().decode('utf-8') try: # Try to check it out; in the common case (nothing # changed) this should be a no-op fork(['git', 'checkout', remote_commit, '--']) except CalledProcessError: # Probably we don't have the commit; fetch it fork([ 'git', 'fetch', '--depth', '1', 'origin', remote_commit ]) # Try again fork(['git', 'checkout', remote_commit, '--']) else: fork(['git', 'fetch']) # If we want to go on a branch, try to find a local branch that tracks it # and use it (possibly with a fast-forward) if self.ref_type == 'branch': # Resolve the remote ref remote_fullref = self.symbolic_full_name(self.ref) # Get a sensible local branch name to try local_ref = remote_fullref.split('/origin/', 1)[1] # Check if it is actually tracking our target try: local_fulltrackref = self.symbolic_full_name( local_ref + "@{u}") except CalledProcessError: # It's fine if it fails - we may not have a local-tracking branch, # so git checkout will do the right thing here local_fulltrackref = remote_fullref if remote_fullref == local_fulltrackref: try: # Checkout and fast-forward fork(['git', 'checkout', local_ref, '--']) fork([ 'git', 'merge', '--ff-only', self.ref, '--' ]) # Final sanity check if log_check_output([ 'git', 'rev-parse', self.ref, '--' ]) != log_check_output( ['git', 'rev-parse', local_ref, '--']): logger.warning( "Warning: your local branch is ahead of required remote branch!" ) return except CalledProcessError: logger.warning( "Couldn't fast-forward local branch, fallback to detached head mode..." ) # General case: plain checkout of the origin ref (going in detached HEAD) fork(['git', 'checkout', self.ref, '--'])