* The command line completion mechanism (in contrib/) learned to load
custom completion file for "git $command" where $command is a
custom "git-$command" that the end user has on the $PATH when using
- newer version of bash.
+ newer version of bash-completion.
* "git send-email" can sometimes offer confirmation dialog "Send this
email?" with choices 'Yes', 'No', 'Quit', and 'All'. A new action
packfile available to itself.
(merge 3737746120 jk/index-pack-maint later to maint).
+ * Make zlib inflate codepath more robust against versions of zlib
+ that clobber unused portion of outbuf.
+ (merge b611396e97 jl/zlib-restore-nul-termination later to maint).
+
+ * Fix old merge glitch in Documentation during v2.13-rc0 era.
+ (merge 28cb06020b mw/doc-merge-enumfix later to maint).
+
* Other minor doc, test and build updates and code cleanups.
(merge 248f66ed8e nd/trace-with-env later to maint).
(merge 14ced5562c ys/bisect-object-id-missing-conversion-fix later to maint).
(merge f156a0934a rd/p4-doc-markup-env later to maint).
(merge 2a00502b14 tg/doc-sec-list later to maint).
(merge 47cc91310a jk/submodule-fsck-loose-fixup later to maint).
+ (merge efde7b725c rd/comment-typofix-in-sha1-file later to maint).
+ (merge 7eedad15df rd/diff-options-typofix later to maint).
+ (merge 58ebd936cc km/doc-workflows-typofix later to maint).
+ (merge 30aa96cdf8 rd/doc-remote-tracking-with-hyphen later to maint).
endif::git-format-patch[]
--indent-heuristic::
- Enable the heuristic that shift diff hunk boundaries to make patches
+ Enable the heuristic that shifts diff hunk boundaries to make patches
easier to read. This is the default.
--no-indent-heuristic::
low-occurrence common elements".
--
+
-For instance, if you configured diff.algorithm variable to a
+For instance, if you configured the `diff.algorithm` variable to a
non-default value and want to use the default one, then you
have to use `--diff-algorithm=default` option.
Warn if changes introduce conflict markers or whitespace errors.
What are considered whitespace errors is controlled by `core.whitespace`
configuration. By default, trailing whitespaces (including
- lines that solely consist of whitespaces) and a space character
+ lines that consist solely of whitespaces) and a space character
that is immediately followed by a tab character inside the
initial indent of the line are considered whitespace errors.
Exits with non-zero status if problems are found. Not compatible
this option is not given, and the configuration variable
`diff.wsErrorHighlight` is not set, only whitespace errors in
`new` lines are highlighted. The whitespace errors are colored
- whith `color.diff.whitespace`.
+ with `color.diff.whitespace`.
endif::git-format-patch[]
discouraged: while possible, it may leave you in a state that is hard to
back out of in the case of a conflict.
-The fourth syntax ("`git merge --continue`") can only be run after the
+The third syntax ("`git merge --continue`") can only be run after the
merge has resulted in conflicts.
OPTIONS
$ git p4 submit topicbranch
------------
+To specify a single commit or a range of commits, use:
+------------
+$ git p4 submit --commit <sha1>
+$ git p4 submit --commit <sha1..sha1>
+------------
+
The upstream reference is generally 'refs/remotes/p4/master', but can
be overridden using the `--origin=` command-line option.
p4/master. See the "Sync options" section above for more
information.
+--commit <sha1>|<sha1..sha1>::
+ Submit only the specified commit or range of commits, instead of the full
+ list of changes that are in the current Git branch.
+
+--disable-rebase::
+ Disable the automatic rebase after all commits have been successfully
+ submitted. Can also be set with git-p4.disableRebase.
+
+--disable-p4sync::
+ Disable the automatic sync of p4/master from Perforce after commits have
+ been submitted. Implies --disable-rebase. Can also be set with
+ git-p4.disableP4Sync. Sync with origin/master still goes ahead if possible.
+
Rebase options
~~~~~~~~~~~~~~
These options can be used to modify 'git p4 rebase' behavior.
Specify submit behavior when a conflict with p4 is found, as per
--conflict. The default behavior is 'ask'.
+git-p4.disableRebase::
+ Do not rebase the tree against p4/master following a submit.
+
+git-p4.disableP4Sync::
+ Do not sync p4/master with Perforce following a submit. Implies git-p4.disableRebase.
+
IMPLEMENTATION DETAILS
----------------------
* Changesets from p4 are imported using Git fast-import.
when following the rules for relative URLs - because the evaluation
of relative URLs in Git is identical to that of relative directories).
+
-The default remote is the remote of the remote tracking branch
-of the current branch. If no such remote tracking branch exists or
+The default remote is the remote of the remote-tracking branch
+of the current branch. If no such remote-tracking branch exists or
the HEAD is detached, "origin" is assumed to be the default remote.
If the superproject doesn't have a default remote configured
the superproject is its own authoritative upstream and the current
.Merge upwards
[caption="Rule: "]
=====================================
-Always commit your fixes to the oldest supported branch that require
+Always commit your fixes to the oldest supported branch that requires
them. Then (periodically) merge the integration branches upwards into each
other.
=====================================
die (_("must end with a color"));
colorfield[colorfield_nr].hop = TIME_MAX;
+ string_list_clear(&l, 0);
}
static void setup_default_color_by_age(void)
* If no sorting parameter is given then we default to sorting
* by 'refname'. This would give us an alphabetically sorted
* array with the 'HEAD' ref at the beginning followed by
- * local branches 'refs/heads/...' and finally remote-tacking
+ * local branches 'refs/heads/...' and finally remote-tracking
* branches 'refs/remotes/...'.
*/
if (!sorting)
}
/**
- * Derives the remote tracking branch from the remote and refspec.
+ * Derives the remote-tracking branch from the remote and refspec.
*
* FIXME: The current implementation assumes the default mapping of
* refs/heads/<branch_name> to refs/remotes/<remote_name>/<branch_name>.
/**
* Given the repo and refspecs, sets fork_point to the point at which the
- * current branch forked from its remote tracking branch. Returns 0 on success,
+ * current branch forked from its remote-tracking branch. Returns 0 on success,
* -1 on failure.
*/
static int get_rebase_fork_point(struct object_id *fork_point, const char *repo,
use strict;
use warnings;
-use autodie;
use Getopt::Long;
use File::Basename;
# Only labels/tags matching this will be imported/exported
defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$'
-# Grab changes in blocks of this many revisions, unless otherwise requested
-defaultBlockSize = 512
+# The block size is reduced automatically if required
+defaultBlockSize = 1<<20
+
+p4_access_checked = False
def p4_build_cmd(cmd):
"""Build a suitable p4 command line.
real_cmd = ' '.join(real_cmd) + ' ' + cmd
else:
real_cmd += cmd
+
+ # now check that we can actually talk to the server
+ global p4_access_checked
+ if not p4_access_checked:
+ p4_access_checked = True # suppress access checks in p4_check_access itself
+ p4_check_access()
+
return real_cmd
def git_dir(path):
if retcode:
raise CalledProcessError(retcode, real_cmd)
+def die_bad_access(s):
+ die("failure accessing depot: {0}".format(s.rstrip()))
+
+def p4_check_access(min_expiration=1):
+ """ Check if we can access Perforce - account still logged in
+ """
+ results = p4CmdList(["login", "-s"])
+
+ if len(results) == 0:
+ # should never get here: always get either some results, or a p4ExitCode
+ assert("could not parse response from perforce")
+
+ result = results[0]
+
+ if 'p4ExitCode' in result:
+ # p4 returned non-zero status, e.g. P4PORT invalid, or p4 not in path
+ die_bad_access("could not run p4")
+
+ code = result.get("code")
+ if not code:
+ # we get here if we couldn't connect and there was nothing to unmarshal
+ die_bad_access("could not connect")
+
+ elif code == "stat":
+ expiry = result.get("TicketExpiration")
+ if expiry:
+ expiry = int(expiry)
+ if expiry > min_expiration:
+ # ok to carry on
+ return
+ else:
+ die_bad_access("perforce ticket expires in {0} seconds".format(expiry))
+
+ else:
+ # account without a timeout - all ok
+ return
+
+ elif code == "error":
+ data = result.get("data")
+ if data:
+ die_bad_access("p4 error: {0}".format(data))
+ else:
+ die_bad_access("unknown error")
+ else:
+ die_bad_access("unknown error code {0}".format(code))
+
_p4_version_string = None
def p4_version_string():
"""Read the version string, showing just the last line, which
# otherwise False.
return mode[-3:] == "755"
+class P4Exception(Exception):
+ """ Base class for exceptions from the p4 client """
+ def __init__(self, exit_code):
+ self.p4ExitCode = exit_code
+
+class P4ServerException(P4Exception):
+ """ Base class for exceptions where we get some kind of marshalled up result from the server """
+ def __init__(self, exit_code, p4_result):
+ super(P4ServerException, self).__init__(exit_code)
+ self.p4_result = p4_result
+ self.code = p4_result[0]['code']
+ self.data = p4_result[0]['data']
+
+class P4RequestSizeException(P4ServerException):
+ """ One of the maxresults or maxscanrows errors """
+ def __init__(self, exit_code, p4_result, limit):
+ super(P4RequestSizeException, self).__init__(exit_code, p4_result)
+ self.limit = limit
+
def isModeExecChanged(src_mode, dst_mode):
return isModeExec(src_mode) != isModeExec(dst_mode)
-def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False):
+def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
+ errors_as_exceptions=False):
if isinstance(cmd,basestring):
cmd = "-G " + cmd
pass
exitCode = p4.wait()
if exitCode != 0:
- entry = {}
- entry["p4ExitCode"] = exitCode
- result.append(entry)
+ if errors_as_exceptions:
+ if len(result) > 0:
+ data = result[0].get('data')
+ if data:
+ m = re.search('Too many rows scanned \(over (\d+)\)', data)
+ if not m:
+ m = re.search('Request too large \(over (\d+)\)', data)
+
+ if m:
+ limit = int(m.group(1))
+ raise P4RequestSizeException(exitCode, result, limit)
+
+ raise P4ServerException(exitCode, result)
+ else:
+ raise P4Exception(exitCode)
+ else:
+ entry = {}
+ entry["p4ExitCode"] = exitCode
+ result.append(entry)
return result
try:
(changeStart, changeEnd) = p4ParseNumericChangeRange(parts)
block_size = chooseBlockSize(requestedBlockSize)
- except:
+ except ValueError:
changeStart = parts[0][1:]
changeEnd = parts[1]
if requestedBlockSize:
changes = set()
# Retrieve changes a block at a time, to prevent running
- # into a MaxResults/MaxScanRows error from the server.
+ # into a MaxResults/MaxScanRows error from the server. If
+ # we _do_ hit one of those errors, turn down the block size
while True:
cmd = ['changes']
for p in depotPaths:
cmd += ["%s...@%s" % (p, revisionRange)]
+ # fetch the changes
+ try:
+ result = p4CmdList(cmd, errors_as_exceptions=True)
+ except P4RequestSizeException as e:
+ if not block_size:
+ block_size = e.limit
+ elif block_size > e.limit:
+ block_size = e.limit
+ else:
+ block_size = max(2, block_size // 2)
+
+ if verbose: print("block size error, retrying with block size {0}".format(block_size))
+ continue
+ except P4Exception as e:
+ die('Error retrieving changes description ({0})'.format(e.p4ExitCode))
+
# Insert changes in chronological order
- for entry in reversed(p4CmdList(cmd)):
- if entry.has_key('p4ExitCode'):
- die('Error retrieving changes descriptions ({})'.format(entry['p4ExitCode']))
+ for entry in reversed(result):
if not entry.has_key('change'):
continue
changes.add(int(entry['change']))
optparse.make_option("--update-shelve", dest="update_shelve", action="append", type="int",
metavar="CHANGELIST",
help="update an existing shelved changelist, implies --shelve, "
- "repeat in-order for multiple shelved changelists")
+ "repeat in-order for multiple shelved changelists"),
+ optparse.make_option("--commit", dest="commit", metavar="COMMIT",
+ help="submit only the specified commit(s), one commit or xxx..xxx"),
+ optparse.make_option("--disable-rebase", dest="disable_rebase", action="store_true",
+ help="Disable rebase after submit is completed. Can be useful if you "
+ "work from a local git branch that is not master"),
+ optparse.make_option("--disable-p4sync", dest="disable_p4sync", action="store_true",
+ help="Skip Perforce sync of p4/master after submit or shelve"),
]
self.description = "Submit changes from git to the perforce depot."
self.usage += " [name of git branch to submit into perforce depot]"
self.dry_run = False
self.shelve = False
self.update_shelve = list()
+ self.commit = ""
+ self.disable_rebase = gitConfigBool("git-p4.disableRebase")
+ self.disable_p4sync = gitConfigBool("git-p4.disableP4Sync")
self.prepare_p4_only = False
self.conflict_behavior = None
self.isWindows = (platform.system() == "Windows")
else:
committish = 'HEAD'
- for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, committish)]):
- commits.append(line.strip())
- commits.reverse()
+ if self.commit != "":
+ if self.commit.find("..") != -1:
+ limits_ish = self.commit.split("..")
+ for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (limits_ish[0], limits_ish[1])]):
+ commits.append(line.strip())
+ commits.reverse()
+ else:
+ commits.append(self.commit)
+ else:
+ for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, committish)]):
+ commits.append(line.strip())
+ commits.reverse()
if self.preserveUser or gitConfigBool("git-p4.skipUserNameCheck"):
self.checkAuthorship = False
sync = P4Sync()
if self.branch:
sync.branch = self.branch
- sync.run([])
+ if self.disable_p4sync:
+ sync.sync_origin_only()
+ else:
+ sync.run([])
- rebase = P4Rebase()
- rebase.rebase()
+ if not self.disable_rebase:
+ rebase = P4Rebase()
+ rebase.rebase()
else:
if len(applied) == 0:
print self.gitError.read()
sys.exit(1)
+ def sync_origin_only(self):
+ if self.syncWithOrigin:
+ self.hasOrigin = originP4BranchesExist()
+ if self.hasOrigin:
+ if not self.silent:
+ print 'Syncing with origin first, using "git fetch origin"'
+ system("git fetch origin")
+
def importHeadRevision(self, revision):
print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
else:
self.refPrefix = "refs/heads/p4/"
- if self.syncWithOrigin:
- self.hasOrigin = originP4BranchesExist()
- if self.hasOrigin:
- if not self.silent:
- print 'Syncing with origin first, using "git fetch origin"'
- system("git fetch origin")
+ self.sync_origin_only()
branch_arg_given = bool(self.branch)
if len(self.branch) == 0:
static struct dir_rename_entry *check_dir_renamed(const char *path,
struct hashmap *dir_renames)
{
- char temp[PATH_MAX];
+ char *temp = xstrdup(path);
char *end;
- struct dir_rename_entry *entry;
+ struct dir_rename_entry *entry = NULL;;
- strcpy(temp, path);
while ((end = strrchr(temp, '/'))) {
*end = '\0';
entry = dir_rename_find_entry(dir_renames, temp);
if (entry)
- return entry;
+ break;
}
- return NULL;
+ free(temp);
+ return entry;
}
static void compute_collisions(struct hashmap *collisions,
return NULL;
}
+ /* versions of zlib can clobber unconsumed portion of outbuf */
+ buffer[size] = '\0';
+
return buffer;
}
/*
* Compute the exact path an alternate is at and returns it. In case of
* error NULL is returned and the human readable error is added to `err`
- * `path` may be relative and should point to $GITDIR.
+ * `path` may be relative and should point to $GIT_DIR.
* `err` must not be null.
*/
char *compute_alternate_path(const char *path, struct strbuf *err)
)
'
+# make two commits, but tell it to apply only one
+
+test_expect_success 'submit --commit one' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ test_commit "file9" &&
+ test_commit "file10" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit --commit HEAD
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing "file9.t" &&
+ test_path_is_file "file10.t"
+ )
+'
+
+# make three commits, but tell it to apply only range
+
+test_expect_success 'submit --commit range' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ test_commit "file11" &&
+ test_commit "file12" &&
+ test_commit "file13" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit --commit HEAD~2..HEAD
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing "file11.t" &&
+ test_path_is_file "file12.t" &&
+ test_path_is_file "file13.t"
+ )
+'
+
#
# Basic submit tests, the five handled cases
#
'
test_expect_success 'Clone repo with multiple depot paths' '
+ test_when_finished cleanup_git &&
(
cd "$git" &&
git p4 clone --changes-block-size=4 //depot/pathA@all //depot/pathB@all \
)
'
+test_expect_success 'Clone repo with self-sizing block size' '
+ test_when_finished cleanup_git &&
+ git p4 clone --changes-block-size=1000000 //depot@all --destination="$git" &&
+ git -C "$git" log --oneline >log &&
+ test_line_count \> 10 log
+'
+
test_expect_success 'kill p4d' '
kill_p4d
'
--- /dev/null
+#!/bin/sh
+
+test_description='git p4 errors'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'add p4 files' '
+ (
+ cd "$cli" &&
+ echo file1 >file1 &&
+ p4 add file1 &&
+ p4 submit -d "file1"
+ )
+'
+
+# after this test, the default user requires a password
+test_expect_success 'error handling' '
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ P4PORT=: test_must_fail git p4 submit 2>errmsg
+ ) &&
+ p4 passwd -P newpassword &&
+ (
+ P4PASSWD=badpassword test_must_fail git p4 clone //depot/foo 2>errmsg &&
+ grep -q "failure accessing depot.*P4PASSWD" errmsg
+ )
+'
+
+test_expect_success 'ticket logged out' '
+ P4TICKETS="$cli/tickets" &&
+ echo "newpassword" | p4 login &&
+ (
+ cd "$git" &&
+ test_commit "ticket-auth-check" &&
+ p4 logout &&
+ test_must_fail git p4 submit 2>errmsg &&
+ grep -q "failure accessing depot" errmsg
+ )
+'
+
+test_expect_success 'create group with short ticket expiry' '
+ P4TICKETS="$cli/tickets" &&
+ echo "newpassword" | p4 login &&
+ p4_add_user short_expiry_user &&
+ p4 -u short_expiry_user passwd -P password &&
+ p4 group -i <<-EOF &&
+ Group: testgroup
+ Timeout: 3
+ Users: short_expiry_user
+ EOF
+
+ p4 users | grep short_expiry_user
+'
+
+test_expect_success 'git operation with expired ticket' '
+ P4TICKETS="$cli/tickets" &&
+ P4USER=short_expiry_user &&
+ echo "password" | p4 login &&
+ (
+ cd "$git" &&
+ git p4 sync &&
+ sleep 5 &&
+ test_must_fail git p4 sync 2>errmsg &&
+ grep "failure accessing depot" errmsg
+ )
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+
+test_done