├── git-bz ├── git-qrebase ├── git-root ├── test.sh ├── .gitmodules ├── git-fix-whitespace ├── LICENSE ├── git-branchname ├── private └── check-for-updates ├── git-qparent ├── git-qapplied ├── CODE_OF_CONDUCT.md ├── git-bzexport ├── git-push-to-trychooser ├── git-push-to-mozreview ├── git-edit-files ├── git-tracks ├── git-push-to-try ├── git-to-hg-commit ├── pre-commit ├── git-remote-link ├── git-new-workdir ├── hg-patch-to-git-patch ├── git-patch-to-hg-patch ├── git-push-to-hg └── README.markdown /git-bz: -------------------------------------------------------------------------------- 1 | git-bz-moz/git-bz -------------------------------------------------------------------------------- /git-qrebase: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | git rebase -i $(git qparent) 3 | -------------------------------------------------------------------------------- /git-root: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git rev-parse --show-toplevel 4 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./git-patch-to-hg-patch --test 4 | echo "Tests complete." 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "git-bz-moz"] 2 | path = git-bz-moz 3 | url = https://github.com/mozilla/git-bz-moz.git 4 | -------------------------------------------------------------------------------- /git-fix-whitespace: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git commit -a --no-verify -m TEMP 4 | git rebase --whitespace=fix HEAD^ 5 | git reset HEAD^ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Except for git-new-workdir, which is covered under GPLv2, the code in this 2 | repository is placed into the public domain via CC0. 3 | 4 | http://creativecommons.org/publicdomain/zero/1.0/legalcode 5 | -------------------------------------------------------------------------------- /git-branchname: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the name of the active git branch. If the cwd is not a git repository, 4 | # output nothing. 5 | # 6 | # This is useful if you want to display the current branch in your shell 7 | # prompt. 8 | 9 | git branch 2>/dev/null | grep '^\*' | sed 's/^\* //' 10 | -------------------------------------------------------------------------------- /private/check-for-updates: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Output a warning if we're not at the newest upstream version, and kick off a 4 | # background fetch. It'll take two calls to check-for-updates before we notice 5 | # that we're out of date, but that's OK. 6 | 7 | cd $(dirname $0) 8 | 9 | if [[ $(git log HEAD..origin/master) != "" ]]; then 10 | echo "You are not using the latest revision of moz-git-tools. Consider updating your tree." 1>&2 11 | fi 12 | git fetch --quiet origin & 13 | -------------------------------------------------------------------------------- /git-qparent: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: git qparent [rev] 4 | # 5 | # Like |hg qparent|, gives last common revision between |rev| and upstream. If 6 | # |rev| is not given, we let rev equal HEAD. 7 | # 8 | # The upstream branch is set via git branch --set-upstream . 9 | # If no upstream branch has been explicitly set, we use origin/master. 10 | 11 | set -e 12 | 13 | export PATH="$(dirname "$0"):$PATH" 14 | 15 | if [[ "$1" == "" ]]; then 16 | git merge-base HEAD "$(git tracks -d)" 17 | else 18 | git merge-base "$1" "$(git tracks -d "$1")" 19 | fi 20 | -------------------------------------------------------------------------------- /git-qapplied: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # USAGE: git qapplied [branch] 4 | # 5 | # As an analog to |hg qapplied|, |git qapplied| lists the commits in the given 6 | # branch which are not in the master branch. If a branch is not given, the 7 | # branch is the current branch. 8 | 9 | set -e 10 | 11 | export PATH="$(dirname "$0"):$PATH" 12 | 13 | branch="$1" 14 | [ ! -z "$branch" ] || branch="$(git rev-parse --abbrev-ref HEAD)" 15 | 16 | echo "Branch $branch (downstream from $(git tracks -d $branch))" 17 | git log --reverse --date-order --pretty=oneline --abbrev-commit "$(git qparent "$branch")".."$branch" 18 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /git-bzexport: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Uses push-to-hg to push revision(s) to the specified hg repository, and 5 | # then runs the hg "bzexport" command for uploading patches to Bugzilla. 6 | 7 | THIS_DIR=$(cd `dirname "$0"`; pwd) 8 | PATH="$THIS_DIR:$PATH" 9 | 10 | hg_cmd() { 11 | # Don't suppress HG output since trychooser uses prompts 12 | hg -R "$hg_repo" $@ 13 | } 14 | 15 | hg_repo="$1" 16 | if [[ "$1" == "-t" || "$1" == "--tip" ]]; then 17 | hg_repo="$2" 18 | fi 19 | 20 | if [[ "$hg_repo" == "" ]]; then 21 | echo "Usage: $(basename "$0") [-t/--tip] path-to-hg-repo [git-revs]" 1>& 2 22 | exit 255 23 | fi 24 | 25 | PREVIOUS_QUEUE=$(hg_cmd qqueue --active) 26 | 27 | git push-to-hg $@ 28 | 29 | hg_cmd bzexport -e 30 | hg_cmd -q qpop -a > /dev/null 31 | hg_cmd qqueue $PREVIOUS_QUEUE 32 | -------------------------------------------------------------------------------- /git-push-to-trychooser: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Uses push-to-hg to push revision(s) to the specified hg repository, and 5 | # then runs the hg "trychooser" command for pushing the commits to the tryserver. 6 | 7 | THIS_DIR=$(cd `dirname "$0"`; pwd) 8 | TRYCHOOSER="$THIS_DIR/trychooser/trychooser" 9 | PATH="$THIS_DIR:$PATH" 10 | 11 | hg_cmd() { 12 | # Don't suppress HG output since trychooser uses prompts 13 | hg -R "$hg_repo" $@ 14 | } 15 | 16 | hg_repo="$1" 17 | if [[ "$1" == "-t" || "$1" == "--tip" ]]; then 18 | hg_repo="$2" 19 | fi 20 | 21 | if [[ "$hg_repo" == "" ]]; then 22 | echo "Usage: $(basename "$0") [-t/--tip] path-to-hg-repo [git-revs]" 1>& 2 23 | exit 255 24 | fi 25 | 26 | PREVIOUS_QUEUE=$(hg_cmd qqueue --active) 27 | 28 | git push-to-hg $@ 29 | 30 | hg_cmd trychooser 31 | hg_cmd -q qpop -a > /dev/null 32 | hg_cmd qqueue "$PREVIOUS_QUEUE" 33 | -------------------------------------------------------------------------------- /git-push-to-mozreview: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Usage: git-push-to-mozreview [-t|--tip] [-r REVS|--rev REVS] HG_REPO 4 | 5 | set -e 6 | PATH=$(dirname $0):$PATH 7 | 8 | # Sigh, command-line parsing in bash is so much fun. 9 | 10 | while true; do 11 | if [[ "$1" == "-t" || "$1" == "--tip" ]]; then 12 | tip_cmd="--tip" 13 | shift 14 | continue 15 | fi 16 | 17 | if [[ "$1" == "-r" || "$1" == "--rev" ]]; then 18 | revs="$2" 19 | shift 20 | shift 21 | continue 22 | fi 23 | 24 | break 25 | done 26 | 27 | hg_repo=$1 28 | if [[ -n "$hg_repo" ]]; then 29 | shift 30 | fi 31 | 32 | if [[ -z "$hg_repo" ]]; then 33 | echo "usage: $(basename "$0") [-t|--tip] [-r REVS|--rev REVS] HG_REPO" 34 | exit 255 35 | fi 36 | 37 | git-push-to-hg $tip_cmd "$hg_repo" "$revs" 38 | 39 | hg -R "$hg_repo" push -f -r tip ssh://reviewboard-hg.mozilla.org/autoreview 40 | 41 | hg -R "$hg_repo" -q qpop -a > /dev/null 42 | -------------------------------------------------------------------------------- /git-edit-files: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "$1" == "--help" ]]; then 4 | echo "Usage: $0 [GIT_REV] 5 | Invoke \$EDITOR on the files modified in the given revision. 6 | If no revision is given, invoke \$EDITOR on the files modified in git status." 7 | exit 1 8 | fi 9 | 10 | # The diff filter is everything except D; we don't want to edit deleted files! 11 | diff_params="--diff-filter=ACMRTUXB --name-only" 12 | 13 | if [[ "$1" == "" ]]; then 14 | files=$(git diff $diff_params) 15 | if [[ "$files" == "" ]]; then 16 | echo "No files modified in current checkout." 17 | echo "Did you mean to do |git edit-files REV|?" 18 | exit 2 19 | fi 20 | 21 | $EDITOR $files 22 | exit 0 23 | fi 24 | 25 | if ! echo "$1" | grep -q '\.\.'; then 26 | revs="$1^..$1" 27 | else 28 | revs="$1" 29 | fi 30 | 31 | files=$(git diff $diff_params $revs) 32 | if [[ "$files" == "" ]]; then 33 | echo "No files modified in range $revs." 34 | exit 2 35 | fi 36 | 37 | $EDITOR $files 38 | -------------------------------------------------------------------------------- /git-tracks: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Usage: git tracks [branch] 4 | # 5 | # Get the upstream branch of the given branch. If no branch is given, get the 6 | # upstream branch of the currrent branch. 7 | # 8 | # If -d/--default is passed, outputs origin/master if there's no upstream 9 | # branch. 10 | # 11 | # To set 's upstream branch, run 12 | # 13 | # $ git branch --set-upstream 14 | # 15 | # 16 | # Based on http://serverfault.com/a/352236/38694 17 | 18 | if [[ "$1" == "-d" || "$1" == "--default" ]]; then 19 | use_default=1 20 | shift 21 | else 22 | use_default=0 23 | fi 24 | 25 | if [[ "$1" == "" ]]; then 26 | branch=HEAD 27 | else 28 | branch=$1 29 | fi 30 | 31 | branchref=$(git rev-parse --symbolic-full-name $branch) || exit $? 32 | remote=$(git for-each-ref --format='%(upstream:short)' $branchref) 33 | if [[ $remote != "" ]]; then 34 | echo $remote 35 | elif [[ "$use_default" == 1 ]] && git rev-parse origin/master &>/dev/null; then 36 | echo origin/master 37 | else 38 | echo >&2 "Warning: Could not find a upstream for $branch" 39 | fi 40 | -------------------------------------------------------------------------------- /git-push-to-try: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Usage: git-push-to-try [-t|--tip] [-r REVS|--rev REVS] HG_REPO TRY_PARAMS 4 | 5 | set -e 6 | PATH=$(dirname $0):$PATH 7 | 8 | # Sigh, command-line parsing in bash is so much fun. 9 | 10 | while true; do 11 | if [[ "$1" == "-t" || "$1" == "--tip" ]]; then 12 | tip_cmd="--tip" 13 | shift 14 | continue 15 | fi 16 | 17 | if [[ "$1" == "-r" || "$1" == "--rev" ]]; then 18 | revs="$2" 19 | shift 20 | shift 21 | continue 22 | fi 23 | 24 | break 25 | done 26 | 27 | hg_repo=$1 28 | if [[ -n "$hg_repo" ]]; then 29 | shift 30 | fi 31 | 32 | if [[ -z "$hg_repo" || -z "$@" ]]; then 33 | echo "usage: $(basename "$0") [-t|--tip] [-r REVS|--rev REVS] HG_REPO TRY_PARAMS" 34 | exit 255 35 | fi 36 | 37 | git-push-to-hg $tip_cmd "$hg_repo" "$revs" 38 | 39 | hg -R "$hg_repo" -q qnew try -m "try: $*" > /dev/null 40 | echo "try: $@" 41 | 42 | hg -R "$hg_repo" push -f -r tip ssh://hg.mozilla.org/try 43 | echo 44 | echo "https://treeherder.mozilla.org/#/jobs?repo=try&revision=$(hg -R "$hg_repo" log -l1 --template "{node|short}")" 45 | 46 | hg -R "$hg_repo" -q qpop -a > /dev/null 47 | -------------------------------------------------------------------------------- /git-to-hg-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | hg_repo=$1 5 | git_commit_id=$2 6 | 7 | if [[ "$git_commit_id" == "" ]]; then 8 | git_commit_id="HEAD" 9 | fi 10 | 11 | if [[ "$git_commit_id" == "" || "$hg_repo" == "" ]]; then 12 | echo "Usage: $(basename $0) path-to-hg-repo [path-to-git-repo] [git-commit-id]" 1>&2 13 | exit 255 14 | fi 15 | 16 | function git_log() 17 | { 18 | # $1: The format to pass to git log. 19 | git log -n1 "$git_commit_id" --pretty="format:$1" 20 | } 21 | 22 | # If git doesn't have an e-mail address, it substitutes "", which will mess us up. 23 | commit_user=$(git_log '%an <%ae>' | sed -e 's/ //') 24 | commit_date=$(git_log '%aD') 25 | search_str=$(git_log ' %B' | head -n1) 26 | 27 | # Locate the hg {desc}|{node} pair containing the commit message from search_str. 28 | hg_str=$(hg log -R "$hg_repo" --user "$commit_user" --date "$commit_date" --template '{node} {desc|firstline}\n' | grep -F "$search_str") || true 29 | 30 | if [[ "$hg_str" == "" ]]; then 31 | echo "No matching commit found." 1>&2 32 | exit 1 33 | elif (( "$(echo "$hg_str" | wc -l)" > 1 )); then 34 | echo "Uh oh; multiple matching commits!" 1>&2 35 | echo "$hg_str" 1>&2 36 | exit 2 37 | fi 38 | 39 | # Cool; we found it! 40 | hg_node=$(echo "$hg_str" | cut -f 1 -d ' ') 41 | echo "$hg_node" 42 | -------------------------------------------------------------------------------- /pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Check that we're not adding any .orig files, and check for trailing 4 | # whitespace. 5 | # 6 | 7 | if git rev-parse --verify HEAD >/dev/null 2>&1 8 | then 9 | against=HEAD 10 | else 11 | # Initial commit: diff against an empty tree object 12 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 13 | fi 14 | 15 | # Check for .orig files added in this commit. 16 | orig_files=$(git diff --cached --name-only --diff-filter=A $against | grep '\.orig$') 17 | if [[ "$orig_files" != "" ]]; then 18 | num_orig_files=$(echo "$orig_files" | wc -l) 19 | if [[ "$num_orig_files" == "1" ]]; then 20 | echo "Error: Attempting to commit .orig file $orig_files." 21 | else 22 | echo "Error: Attempting to commit $num_orig_files .orig files:" 23 | echo 24 | echo "$(echo "$orig_files" | sed -e 's/^/ /')" 25 | fi 26 | echo 27 | echo "If you really want to proceed, commit with --no-verify." 28 | exit 1 29 | fi 30 | 31 | # Check for trailing whitespace 32 | if ! git diff-index --check --cached $against --; then 33 | echo 34 | echo "Error: Trailing whitespace in commit." 35 | echo 36 | echo "You can automagically fix trailing whitespace with: " 37 | echo 38 | echo " git commit -a --no-verify -m TEMP" 39 | echo " git rebase --whitespace=fix HEAD^" 40 | echo " git reset HEAD^" 41 | echo 42 | echo "If you really want to proceed, commit with --no-verify." 43 | exit 2 44 | fi 45 | -------------------------------------------------------------------------------- /git-remote-link: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | """Output a link to a particular file+line in a remote repository. 4 | 5 | Right now this works only for repos downstream from github.""" 6 | 7 | from __future__ import print_function 8 | import os 9 | import sys 10 | import argparse 11 | import subprocess 12 | import re 13 | 14 | def get_filename_and_lineno(args): 15 | split = args.file.split(':') 16 | if len(split) > 2: 17 | print('Too many ":"s in filename %s; max is 1', file=sys.stderr) 18 | sys.exit(1) 19 | filename = split[0] 20 | if len(split) == 1: 21 | lineno = 0 22 | else: 23 | try: 24 | lineno = int(split[1]) 25 | except: 26 | print('Value in filename after ":" must be an integer.', file=sys.stderr) 27 | return (filename, lineno) 28 | 29 | def git(*args): 30 | return subprocess.check_output(['git'] + list(args)).strip() 31 | 32 | def main(args): 33 | (filename, lineno) = get_filename_and_lineno(args) 34 | gitdir = git('rev-parse', '--show-toplevel') 35 | rel_filename = os.path.relpath(os.path.abspath(filename), gitdir) 36 | 37 | upstream_branch = git('tracks') 38 | upstream_repo = upstream_branch.split('/')[0] 39 | 40 | remote_info = git('remote', 'show', '-n', upstream_repo) 41 | fetch_line = remote_info.split('\n')[1] 42 | fetch_url = re.match('\s*Fetch URL:\s*(.*)', fetch_line).group(1) 43 | 44 | # github ssh URL --> https URL 45 | fetch_url = re.sub('^git@github.com:', 'https://github.com/', fetch_url) 46 | 47 | fetch_url = re.sub('^git://', 'https://', fetch_url) 48 | 49 | # Strip off trailing .git 50 | base_url = re.sub('.git$', '', fetch_url) 51 | 52 | upstream_commit = git('qparent') 53 | 54 | url = '%s/tree/%s/%s' % (base_url, git('qparent'), rel_filename) 55 | if lineno: 56 | url = '%s#L%d' % (url, lineno) 57 | print(url) 58 | 59 | if __name__ == '__main__': 60 | parser = argparse.ArgumentParser(description=__doc__) 61 | parser.add_argument('file', metavar='FILE', 62 | help='Either path/to/file or path/to/file:lineno') 63 | args = parser.parse_args() 64 | main(args) 65 | -------------------------------------------------------------------------------- /git-new-workdir: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copied verbatim from 4 | # http://git.kernel.org/?p=git/git.git;a=blob_plain;f=contrib/workdir/git-new-workdir 5 | # 6 | # This file is covered under GPLv2 (and /not/ later versions). See 7 | # http://git.kernel.org/cgit/git/git.git/plain/COPYING 8 | 9 | usage () { 10 | echo "usage:" $@ 11 | exit 127 12 | } 13 | 14 | die () { 15 | echo $@ 16 | exit 128 17 | } 18 | 19 | if test $# -lt 2 || test $# -gt 3 20 | then 21 | usage "$0 []" 22 | fi 23 | 24 | orig_git=$1 25 | new_workdir=$2 26 | branch=$3 27 | 28 | # want to make sure that what is pointed to has a .git directory ... 29 | git_dir=$(cd "$orig_git" 2>/dev/null && 30 | git rev-parse --git-dir 2>/dev/null) || 31 | die "Not a git repository: \"$orig_git\"" 32 | 33 | case "$git_dir" in 34 | .git) 35 | git_dir="$orig_git/.git" 36 | ;; 37 | .) 38 | git_dir=$orig_git 39 | ;; 40 | esac 41 | 42 | # don't link to a configured bare repository 43 | isbare=$(git --git-dir="$git_dir" config --bool --get core.bare) 44 | if test ztrue = z$isbare 45 | then 46 | die "\"$git_dir\" has core.bare set to true," \ 47 | " remove from \"$git_dir/config\" to use $0" 48 | fi 49 | 50 | # don't link to a workdir 51 | if test -h "$git_dir/config" 52 | then 53 | die "\"$orig_git\" is a working directory only, please specify" \ 54 | "a complete repository." 55 | fi 56 | 57 | # don't recreate a workdir over an existing repository 58 | if test -e "$new_workdir" 59 | then 60 | die "destination directory '$new_workdir' already exists." 61 | fi 62 | 63 | # make sure the links use full paths 64 | git_dir=$(cd "$git_dir"; pwd) 65 | 66 | # create the workdir 67 | mkdir -p "$new_workdir/.git" || die "unable to create \"$new_workdir\"!" 68 | 69 | # create the links to the original repo. explicitly exclude index, HEAD and 70 | # logs/HEAD from the list since they are purely related to the current working 71 | # directory, and should not be shared. 72 | for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache svn 73 | do 74 | case $x in 75 | */*) 76 | mkdir -p "$(dirname "$new_workdir/.git/$x")" 77 | ;; 78 | esac 79 | ln -s "$git_dir/$x" "$new_workdir/.git/$x" 80 | done 81 | 82 | # now setup the workdir 83 | cd "$new_workdir" 84 | # copy the HEAD from the original repository as a default branch 85 | cp "$git_dir/HEAD" .git/HEAD 86 | # checkout the branch (either the same as HEAD from the original repository, or 87 | # the one that was asked for) 88 | git checkout -f $branch 89 | -------------------------------------------------------------------------------- /hg-patch-to-git-patch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | r'''Convert an hg-exported patch to a patch suitable for use by git am. 4 | 5 | >>> hg_patch_to_git_patch(StringIO('# HG changeset patch\n# User Foo \n# Node ID deadbeef\n# Parent cafebabe\nCommit\n\nMsg\n\ndiff -\ndiffdiff')) 6 | From: Foo 7 | Subject: Commit 8 | 9 | Msg 10 | 11 | diff - 12 | diffdiff 13 | 14 | ''' 15 | 16 | from __future__ import print_function 17 | import sys 18 | from io import StringIO 19 | 20 | def hg_patch_to_git_patch(hg_patch_file): 21 | hg_patch = hg_patch_file.read().split('\n') 22 | 23 | # First, skip any blank lines. 24 | for i in range(len(hg_patch)): 25 | line = hg_patch[i] 26 | if line != "": 27 | break 28 | 29 | author = None 30 | date = None # not currently used 31 | for i in range(i, len(hg_patch)): 32 | line = hg_patch[i] 33 | if not line.startswith('#'): 34 | break 35 | 36 | if line.startswith('# User '): 37 | author = line[len('# User '):] 38 | if line.startswith('# Date '): 39 | date = line[len('# Date '):] 40 | epoch, utcoffset = date.split() 41 | # Mercurial's timezone offsets are negative and in seconds 42 | # We could also convert the epoch to RFC822 format, but 43 | # git am is happy with it. 44 | utcoffset = int(utcoffset) / 60 45 | date = '%s %s%02d%02d' % ( 46 | epoch, 47 | '+' if utcoffset <= 0 else '-', 48 | abs(utcoffset) // 60, 49 | abs(utcoffset) % 60, 50 | ) 51 | 52 | commit_msg = [] 53 | for i in range(i, len(hg_patch)): 54 | line = hg_patch[i] 55 | if line.startswith('diff -'): 56 | break 57 | commit_msg.append(hg_patch[i]) 58 | 59 | if len(commit_msg) == 1 and not commit_msg[0].strip(): 60 | commit_msg[0] = hg_patch_file.name 61 | 62 | if len(commit_msg) > 1 and not commit_msg[1].strip(): 63 | del commit_msg[1] 64 | 65 | # Remove blank lines at the end of the commit message. 66 | while commit_msg and not commit_msg[-1].strip(): 67 | del commit_msg[-1] 68 | 69 | diff = hg_patch[i:] 70 | 71 | if author: 72 | # XXX ensure this has the Foo form. 73 | # XXX Do I have to worry about text encoding here? 74 | print('From: %s' % author) 75 | else: 76 | print('From: unknown@unknown.com') 77 | if date: 78 | print('Date: %s' % date) 79 | if commit_msg: 80 | print('Subject: %s' % commit_msg[0]) 81 | 82 | print() 83 | print('\n'.join(commit_msg[1:])) 84 | print() 85 | print('\n'.join(diff)) 86 | 87 | if __name__ == '__main__': 88 | if len(sys.argv) > 1 and sys.argv[1] == '--test': 89 | import doctest 90 | doctest.testmod() 91 | sys.exit(0) 92 | 93 | if len(sys.argv) == 1: 94 | file = sys.stdin 95 | elif len(sys.argv) == 2: 96 | file = open(sys.argv[1], 'r') 97 | else: 98 | print('Error: Specify one file, or pipe input on stdin.') 99 | sys.exit(1) 100 | 101 | hg_patch_to_git_patch(file) 102 | -------------------------------------------------------------------------------- /git-patch-to-hg-patch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | r"""Git format-patch to hg importable patch. 4 | 5 | (Who knew this was so complicated?) 6 | 7 | >>> process(StringIO('From 3ce1ccc06 Mon Sep 17 00:00:00 2001\nFrom: fromuser \nSubject: subject\n\nRest of patch.\nMore patch.\n')) 8 | '# HG changeset patch\n# User fromuser \n\nsubject\n\nRest of patch.\nMore patch.\n' 9 | 10 | >>> process(StringIO('From 3ce1ccc06 Mon Sep 17 00:00:00 2001\nFrom: "A. J. Quoted" \nSubject: subject\n\nRest of patch.\nMore patch.\n')) 11 | '# HG changeset patch\n# User A. J. Quoted \n\nsubject\n\nRest of patch.\nMore patch.\n' 12 | 13 | >>> process(StringIO('From: fromuser \nSubject: A very long subject line. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi faucibus, arcu sit amet\n\nRest of patch.\nMore patch.\n')) 14 | '# HG changeset patch\n# User fromuser \n\nA very long subject line. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi faucibus, arcu sit amet\n\nRest of patch.\nMore patch.\n' 15 | 16 | >>> process(StringIO('From: f \nSubject: =?UTF-8?q?Bug=20655877=20-=20Dont=20treat=20SVG=20text=20frames=20?= =?UTF-8?q?as=20being=20positioned.=20r=3D=3F?=\n\nPatch.')) 17 | '# HG changeset patch\n# User f \n\nBug 655877 - Dont treat SVG text frames as being positioned. r=?\n\nPatch.' 18 | """ 19 | 20 | # Original author: bholley 21 | 22 | import sys 23 | import re 24 | import fileinput 25 | import email, email.parser, email.header, email.utils 26 | import math 27 | from io import StringIO 28 | from itertools import takewhile 29 | 30 | def decode_header(hdr_string): 31 | r"""Clean up weird encoding crap. 32 | 33 | >>> clean_header('[PATCH] =?UTF-8?q?Bug=20655877=20r=3D=3F?=') 34 | '[PATCH] Bug 655877 r=?' 35 | """ 36 | rv = [] 37 | hdr = email.header.Header(hdr_string, maxlinelen=float('inf')) 38 | for (part, encoding) in email.header.decode_header(hdr): 39 | if encoding is None: 40 | rv.append(part) 41 | else: 42 | rv.append(part.decode(encoding)) 43 | return ' '.join(rv) 44 | 45 | def clean_header(hdr_string): 46 | r"""Transform a header split over many lines into a header split only where 47 | linebreaks are intended. This is important because hg cares about the first 48 | line of the commit message. 49 | 50 | Also clean up weird encoding crap. 51 | 52 | >>> clean_header('Foo\n bar\n baz') 53 | 'Foo bar baz' 54 | >>> clean_header('Foo\n bar\nSpam\nEggs') 55 | 'Foo bar\nSpam\nEggs' 56 | """ 57 | 58 | lines = [] 59 | curline = '' 60 | for line in decode_header(hdr_string).split('\n'): 61 | if not line.startswith(' '): 62 | lines.append(curline) 63 | curline = '' 64 | curline += line 65 | lines.append(curline) 66 | return '\n'.join(lines[1:]) 67 | 68 | def process(git_patch_file): 69 | parser = email.parser.Parser() 70 | msg = parser.parse(git_patch_file) 71 | from_hdr = clean_header(msg['From']) 72 | commit_title = clean_header(msg['subject']) 73 | if not len(commit_title) or not len(from_hdr): 74 | sys.stderr.write("%s does not look like a valid git patch file, skipping\n" 75 | % git_patch_file.name) 76 | return 77 | 78 | parsed_from = email.utils.parseaddr(from_hdr) 79 | nuke_prefix = r"\[PATCH( \d+/\d+)?\] " 80 | match = re.match(nuke_prefix, commit_title) 81 | if match: 82 | commit_title = commit_title[match.end():] 83 | 84 | patch_body = msg.get_payload() 85 | 86 | # git format-patch wraps the diff (including trailing whitespace): 87 | # --- 88 | # 89 | # -- 90 | # 2.0.3 91 | # This doesn't hurt parsing the diff at all, but the version number is 92 | # nonsense once the git specific items have been stripped 93 | patch_body = re.sub(r'--\s?\n[0-9\.]+\n$', '', patch_body) 94 | 95 | return '\n'.join(['# HG changeset patch', 96 | '# User %s <%s>' % parsed_from, 97 | '', 98 | commit_title, 99 | '', 100 | patch_body]) 101 | 102 | if __name__ == "__main__": 103 | if len(sys.argv) > 1 and sys.argv[1] == '--test': 104 | import doctest 105 | doctest.testmod() 106 | sys.exit(0) 107 | 108 | # If there were no arguments, do stdin->stdout. 109 | filelist = sys.argv[1:] 110 | if not filelist: 111 | lines = process(sys.stdin) 112 | sys.stdout.writelines(lines) 113 | sys.exit(0) 114 | 115 | # Otherwise, we take a list of files. 116 | for filename in filelist: 117 | 118 | # Read the lines. 119 | f = open(filename, 'r') 120 | lines = process(f) 121 | f.close() 122 | 123 | # Process. 124 | 125 | if lines: 126 | # Write them back to the same file. 127 | f = open(filename, 'w') 128 | f.writelines(lines) 129 | f.close() 130 | -------------------------------------------------------------------------------- /git-push-to-hg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if ! hash python3 2> /dev/null; then 5 | echo "'python3' is required but not found. Aborting." 6 | if hash python 2> /dev/null; then 7 | echo "'python' found at '$(which python)'." 8 | echo "If using mozilla-build, you can copy it to 'python2.exe' at the same location." 9 | echo "See README.markdown for more information." 10 | fi 11 | fi 12 | 13 | # Push some commits from git to the git-temp qqueue in a given hg repository. 14 | # Note that this nukes the git-temp qqueue, if it exists. 15 | # 16 | # If no commit range is specified, we push 17 | # 18 | # $(git merge-base $(git-tracks -d) HEAD)..HEAD. 19 | # 20 | # Otherwise, we push the commit(s) given. 21 | 22 | this="$0" 23 | if [[ $OSTYPE == "msys" ]]; then 24 | # mozbuild uses msys (which uses "/c/somedir" paths), but git seems to be 25 | # based on cygwin (which uses "c:/somedir") - but markh *actually* sees 26 | # "c:/somedir\filename" - which upsets most $0 usage. 27 | # So fix it up and store the result in $this 28 | # git-for-windows 2+ uses the same convention as msys(1) in mozilla-build: /c/ 29 | if [[ ${this:1:1} == ":" ]]; then 30 | this="/${this/:/}" # change e.g. "c:" to "/c" 31 | fi 32 | this="${this/\\//}" # change "\" to "/" 33 | fi 34 | 35 | PATH="$(dirname $this):$PATH" 36 | 37 | $(dirname $this)/private/check-for-updates 38 | 39 | # Read git version and note if it's less than 1.7.6, because older versions do 40 | # not support git-format-patch --quiet properly. 41 | 42 | function git_version() { 43 | # Get the $1'th part of the git version. 44 | git version | cut -f 3 -d ' ' | cut -f $1 -d '.' 45 | } 46 | 47 | # "Old git" means less than 1.7.5. Earlier versions do not work well with git 48 | # diff --quiet. See https://github.com/jlebar/moz-git-tools/issues/1. 49 | if (( ($(git_version 1) == 1 && $(git_version 2) == 7 && $(git_version 3) < 5) || 50 | ($(git_version 1) == 1 && $(git_version 2) < 7) )); then 51 | old_git=1 52 | else 53 | old_git=0 54 | fi 55 | 56 | push_to_tip=0 57 | if [[ "$1" == "-t" || "$1" == "--tip" ]]; then 58 | push_to_tip=1 59 | shift 60 | fi 61 | 62 | hg_repo="$1" 63 | if [[ "$1" == "" ]]; then 64 | echo "Usage: $(basename $this) [-t/--tip] path-to-hg-repo [git-revs]" 1>& 2 65 | exit 255 66 | fi 67 | 68 | revs="$2" 69 | if [[ "$revs" == "" ]]; then 70 | revs="$(git merge-base HEAD $(git tracks -d))..HEAD" 71 | fi 72 | 73 | # If revs doesn't contain "..", add "$revs^.." to the beginning. 74 | # git-format-patch needs this, otherwise it'll format all patches since $revs. 75 | if ! echo "$revs" | fgrep ".."; then 76 | revs="$revs^..$revs" 77 | fi 78 | 79 | # Check that $revs is a linear sequence of commits. We don't support anything 80 | # else, at the moment. 81 | if [[ "$(git log --merges $revs)" != "" ]]; then 82 | echo "" 83 | echo "It looks like the commit range we're trying to push contains merges." 84 | echo "We do not currently support this; sorry! As a workaround, fold your" 85 | echo "commits." 86 | echo "" 87 | echo "Run " 88 | echo "" 89 | echo " $ git log --merges $revs" 90 | echo "" 91 | echo "to see the list of the offending merge commits." 92 | exit 1 93 | fi 94 | 95 | echo "On branch $(git branch | grep '^\*' | cut -f 2 -d ' ')." 96 | git --no-pager log --reverse --date-order --pretty=oneline --abbrev-commit $revs 97 | 98 | git_status=$(git status --porcelain) 99 | if [[ "$git_status" != "" ]]; then 100 | echo "" 101 | echo "Warning; tree has uncommitted changes! The following changes will " 102 | echo "not be pushed." 103 | echo "$git_status" 104 | read -sn1 -p "Press any key to continue, or to quit. " 105 | echo "" 106 | fi 107 | 108 | function hg_cmd() { 109 | #echo "hg "$@"" >&2 110 | hg -R "$hg_repo" -q $@ > /dev/null 111 | } 112 | 113 | first_rev=$(echo "$revs" | sed -e 's/\.\..*//') 114 | git_parent_rev=$(git merge-base $first_rev $(git tracks -d)) 115 | 116 | # Run git-to-hg-commit, and only run hg pull if it fails. 117 | if [[ "$push_to_tip" == "0" ]]; then 118 | hg_parent_rev=$(git-to-hg-commit "$hg_repo" $git_parent_rev 2> /dev/null || \ 119 | (hg_cmd pull && \ 120 | git-to-hg-commit "$hg_repo" $git_parent_rev 2> /dev/null || \ 121 | (echo "No matching commit found. (Try pushing with --tip.)" 1>& 2 && \ 122 | exit 2))) 123 | else 124 | hg_parent_rev="tip" 125 | fi 126 | 127 | hg_cmd up --rev "$hg_parent_rev" 128 | 129 | # Run qpop -a only if there are patches applied, so we don't see "no patches 130 | # applied". (We could qpop -a and ignore stderr, but then we could ignore a 131 | # real error!) 132 | if [[ $(hg -R "$hg_repo" qapplied) != "" ]]; then 133 | hg_cmd qpop -a 134 | fi 135 | 136 | # Switch to the patches queue (which we assume exists) so we can delete 137 | # git-temp. 138 | hg_cmd qqueue patches 139 | hg_cmd qqueue -q --purge git-temp || true 140 | hg_cmd qqueue -q --create git-temp 141 | 142 | # If we passed --tip, do hg pull && hg up. (This isn't the same as hg pull -u: 143 | # if there's nothing to pull, hg pull -u will skip the update!) 144 | if [[ "$push_to_tip" != 0 ]]; then 145 | hg_cmd pull 146 | hg_cmd up --check 147 | fi 148 | 149 | # Pass --quiet only for not-old-git. 150 | if [[ "$old_git" == 0 ]]; then 151 | git_format_quiet='--quiet' 152 | fi 153 | 154 | git format-patch $git_format_quiet -M -C -U8 -pk $revs -o ""$hg_repo"/.hg/patches-git-temp" 155 | 156 | pushd ""$hg_repo"/.hg/patches-git-temp" > /dev/null 157 | find . -name '*.patch' | sort -g > series 158 | 159 | # There might not be any patches here, in which case git-patch-to-hg-patch 160 | # will hang! 161 | if [[ "$(cat series)" != "" ]]; then 162 | git-patch-to-hg-patch $(cat series) 163 | fi 164 | popd > /dev/null 165 | 166 | hg_cmd qpush -a 167 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Tools for working with Git at Mozilla. 2 | 3 | In order to set this up, clone this repository somewhere, 4 | run the `git submodule init` and then `git submodule update` 5 | command, and add your clone to $PATH. 6 | 7 | Some commands require `python2`. If using mozilla-build on windows, `python2` 8 | might be missing, but `python` exists and is actually python 2 (check by running 9 | `python -V`). On such case, you can create a copy of `python.exe` as `python2.exe` 10 | at the same location - probably at `/python/`. 11 | 12 | Many of these tools rely on a notion of your current branch's "upstream 13 | branch". For example, `git push-to-try` pushes to try all patches in your 14 | current branch that aren't upstream. See the `git-tracks` section below for 15 | details on how to change your upstream branch. 16 | 17 | ## git-bz 18 | 19 | Push commits from git to bugzilla. For example, to push the two top commits in 20 | your repository, run 21 | 22 | git bz attach -e HEAD^^.. 23 | 24 | (This will complain if your commits don't mention the same bug number in their 25 | summaries.) 26 | 27 | If a commit's message starts with "FOLD", it will be folded into the previous 28 | commit before pushing to bugzilla. 29 | 30 | (Actually, git-bz can do more than push commits from git to bugzilla. But I 31 | don't use it for anything else, so I'm not sure which of the other features 32 | work with bugzilla.mozilla.org.) 33 | 34 | ## git-bzexport 35 | 36 | Usage: `git bzexport [-t/--tip] PATH_TO_HG_REPO [GIT_REVS]` 37 | 38 | Push commits from git to bugzilla using the `bzexport` Mercurial extension from 39 | the [Mozilla version control tools repo](https://hg.mozilla.org/hgcustom/version-control-tools). 40 | 41 | The `bzexport` extension has some advantages over `bz attach` - for example, if 42 | multiple Bugzilla reviewers match the reviewer name you specify, `bzexport` will 43 | let you choose between them using a menu - so some users may prefer it. 44 | 45 | This command is implemented using `git-push-to-hg`, so see that command's 46 | documentation for more details on the argument syntax. 47 | 48 | ## git-new-workdir 49 | 50 | Create a new working directory based off an existing local git repository. 51 | 52 | ## git-tracks 53 | 54 | Gets the name of the current branch's upstream branch. With `-d` or 55 | `--default`, git-tracks outputs "origin/master" if there's no upstream branch. 56 | 57 | You can set this with `git branch --set-upstream CURRENT_BRANCH 58 | UPSTREAM_BRANCH`. (Don't do `git branch --set-upstream BRANCH`; that won't 59 | work right!) 60 | 61 | Many other tools in this package use `git tracks -d` as your branch's "upstream 62 | branch". 63 | 64 | ## git-to-hg-commit 65 | 66 | Find the hg commit corresponding to a git commit. 67 | 68 | ## git-push-to-hg 69 | 70 | Usage: `git push-to-hg [-t/--tip] PATH_TO_HG_REPO [GIT_REVS]` 71 | 72 | Push commits from git to a new qqueue in an hg repository. If GIT\_REVS is 73 | omitted, push the commits `$(git merge-base HEAD $(git-tracks))..HEAD` (i.e. 74 | everything in the current branch that's not upstream). 75 | 76 | If `-t` or `--tip` is specified, pull and update the hg repository to latest 77 | tip before pushing. Otherwise, update the hg repository to the revision atop 78 | which the git commits are based. 79 | 80 | ## git-push-to-try 81 | 82 | Usage: `git push-to-try [-r/--rev REVISION_OR_RANGE] [-t/--tip] PATH_TO_HG_REPO TRYCHOOSER_PARAMS` 83 | 84 | Push the commits `$(git merge-base HEAD $(git-tracks))..HEAD` (i.e. everything 85 | in the current branch that's not upstream) to try, by way of the given hg 86 | repository. 87 | 88 | If `-r/--rev REVISION_OR_RANGE` is supplied, then push those commits to try 89 | instead of `$(git merge-base HEAD $(git-tracks))..HEAD`. For example, if you are 90 | working on a feature branch that was branched off of master, and want to push 91 | everything on that branch to try, use `--rev master..HEAD`. 92 | 93 | TRYCHOOSER\_PARAMS should be, e.g. `-b do -p all -u all -t none`. 94 | 95 | ## git-push-to-mozreview 96 | 97 | Usage: `git push-to-review [-r/--rev REVISION_OR_RANGE] [-t/--tip] PATH_TO_HG_REPO` 98 | 99 | **PLEASE NOTE** [git-cinnabar combined with 100 | git-mozreview](https://mozilla-version-control-tools.readthedocs.io/en/latest/mozreview/install-git.html) 101 | is the better-supported way to push to mozreview using git! 102 | 103 | Push the commits `$(git merge-base HEAD $(git-tracks))..HEAD` (i.e. everything 104 | in the current branch that's not upstream) to mozreview, by way of the given hg 105 | repository. 106 | 107 | If `-r/--rev REVISION_OR_RANGE` is supplied, then push those commits to mozreview 108 | instead of `$(git merge-base HEAD $(git-tracks))..HEAD`. For example, if you are 109 | working on a feature branch that was branched off of master, and want to push 110 | everything on that branch to try, use `--rev master..HEAD`. 111 | 112 | ## git-push-to-trychooser 113 | 114 | Usage: `git push-to-trychooser [-t/--tip] PATH_TO_HG_REPO [GIT_REVS]` 115 | 116 | The same as `git push-to-hg`, but also runs the interactive trychooser command 117 | before pushing the commits to try from the given hg repository. 118 | 119 | To use this, you must install the `trychooser` Mercurial extension from 120 | [its repository](https://bitbucket.org/sfink/trychooser). (There are some 121 | out-of-date versions of this extension floating around, so be sure to use this 122 | repository.) 123 | 124 | ## git-qparent 125 | 126 | Outputs the last common revision of the current branch and upstream. 127 | (This command is a synonym for `git merge-base HEAD $(git-tracks)`.) 128 | 129 | ## git-qrebase 130 | 131 | An alias for `git rebase -i $(git qparent)`. This lets you interactively 132 | rebase your current branch without moving the commits to a new upstream base. 133 | 134 | ## git-edit-files 135 | 136 | Open all the files modified in the specified rev range in your `$EDITOR`. (If 137 | no rev range is specified, open the files modified in your current checkout.) 138 | 139 | ## git-fix-whitespace 140 | 141 | Eliminate any trailing whitespace from your uncommitted changes. 142 | 143 | Note that this will reset your index; that is, any changes you've `git add`'ed 144 | will need to be added again. But it won't (or at least, shouldn't!) erase 145 | any changes. 146 | 147 | ## git-qapplied 148 | 149 | Like `hg qapplied`, output the commits in this branch which are not upstream. 150 | 151 | ## git-patch-to-hg-patch 152 | 153 | Format a patch from `git format-patch` as an hg patch. 154 | 155 | ## git-branchname 156 | 157 | Output the name of the active git branch, but if there's no git repository 158 | below the cwd, output nothing. This is useful when you want to display the 159 | current branch name on the command line. 160 | 161 | For example, I have in my ~/.bashrc: 162 | 163 | function vcs-branchname() { 164 | git_branch=`git branchname` 165 | if [[ "$git_branch" != "" ]]; then 166 | echo " ($git_branch)" 167 | fi 168 | } 169 | 170 | PROMPT_COLOR="35m" 171 | PROMPT_COMMAND='BRANCH_NAME=`vcs-branchname`' 172 | PS1='\[\033[01;$PROMPT_COLOR\]\u@\h\[\033[00m\]:\[\033[01;$PROMPT_COLOR\]\w\[\033[00m\]$BRANCH_NAME\$ ' 173 | 174 | which makes my prompt look like 175 | 176 | jlebar@hostname:~/current/path (name-of-git-branch)$ 177 | 178 | ## pre-commit 179 | 180 | A pre-commit hook which checks for .orig files and trailing whitespace. 181 | 182 | To install this hook, symlink it into your repository's `.git/hooks` directory (with the name pre-commit). 183 | --------------------------------------------------------------------------------