Merge branch 'js/trace2-doc-fixes'
Trace2 documentation updates.
* js/trace2-doc-fixes:
trace2 docs: add missing full stop
trace2 docs: clarify what `varargs` is all about
trace2 docs: fix a JSON formatted example
trace2 docs: surround more terms in backticks
trace2 docs: "printf" is not an English word
trace2 docs: a couple of grammar fixes
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 44c080e..adb2f1b 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -302,12 +302,12 @@
mergetools-list.made: ../git-mergetool--lib.sh $(wildcard ../mergetools/*)
$(QUIET_GEN) \
- $(SHELL_PATH) -c 'MERGE_TOOLS_DIR=../mergetools && \
+ $(SHELL_PATH) -c 'MERGE_TOOLS_DIR=../mergetools && TOOL_MODE=diff && \
. ../git-mergetool--lib.sh && \
- show_tool_names can_diff "* " || :' >mergetools-diff.txt && \
- $(SHELL_PATH) -c 'MERGE_TOOLS_DIR=../mergetools && \
+ show_tool_names can_diff' | sed -e "s/\([a-z0-9]*\)/\`\1\`;;/" >mergetools-diff.txt && \
+ $(SHELL_PATH) -c 'MERGE_TOOLS_DIR=../mergetools && TOOL_MODE=merge && \
. ../git-mergetool--lib.sh && \
- show_tool_names can_merge "* " || :' >mergetools-merge.txt && \
+ show_tool_names can_merge' | sed -e "s/\([a-z0-9]*\)/\`\1\`;;/" >mergetools-merge.txt && \
date >$@
TRACK_ASCIIDOCFLAGS = $(subst ','\'',$(ASCIIDOC_COMMON):$(ASCIIDOC_HTML):$(ASCIIDOC_DOCBOOK))
diff --git a/Documentation/RelNotes/2.36.1.txt b/Documentation/RelNotes/2.36.1.txt
new file mode 100644
index 0000000..a961709
--- /dev/null
+++ b/Documentation/RelNotes/2.36.1.txt
@@ -0,0 +1,33 @@
+Git v2.36.1 Release Notes
+=========================
+
+Fixes since v2.36
+-----------------
+
+ * "git submodule update" without pathspec should silently skip an
+ uninitialized submodule, but it started to become noisy by mistake.
+
+ * "diff-tree --stdin" has been broken for about a year, but 2.36
+ release broke it even worse by breaking running the command with
+ <pathspec>, which in turn broke "gitk" and got noticed. This has
+ been corrected by aligning its behaviour to that of "log".
+
+ * Regression fix for 2.36 where "git name-rev" started to sometimes
+ reference strings after they are freed.
+
+ * "git show <commit1> <commit2>... -- <pathspec>" lost the pathspec
+ when showing the second and subsequent commits, which has been
+ corrected.
+
+ * "git fast-export -- <pathspec>" lost the pathspec when showing the
+ second and subsequent commits, which has been corrected.
+
+ * "git format-patch <args> -- <pathspec>" lost the pathspec when
+ showing the second and subsequent commits, which has been
+ corrected.
+
+ * Get rid of a bogus and over-eager coccinelle rule.
+
+ * Correct choices of C compilers used in various CI jobs.
+
+Also contains minor documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.37.0.txt b/Documentation/RelNotes/2.37.0.txt
new file mode 100644
index 0000000..ac8fd41
--- /dev/null
+++ b/Documentation/RelNotes/2.37.0.txt
@@ -0,0 +1,90 @@
+Git v2.37 Release Notes
+=======================
+
+UI, Workflows & Features
+
+ * "vimdiff[123]" mergetool drivers have been reimplemented with a
+ more generic layout mechanism.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * The performance of the "untracked cache" feature has been improved
+ when "--untracked-files=<mode>" and "status.showUntrackedFiles"
+ are combined.
+
+
+Fixes since v2.36
+-----------------
+
+ * "git submodule update" without pathspec should silently skip an
+ uninitialized submodule, but it started to become noisy by mistake.
+ (merge 4f1ccef87c gc/submodule-update-part2 later to maint).
+
+ * "diff-tree --stdin" has been broken for about a year, but 2.36
+ release broke it even worse by breaking running the command with
+ <pathspec>, which in turn broke "gitk" and got noticed. This has
+ been corrected by aligning its behaviour to that of "log".
+ (merge f8781bfda3 jc/diff-tree-stdin-fix later to maint).
+
+ * Regression fix for 2.36 where "git name-rev" started to sometimes
+ reference strings after they are freed.
+ (merge 45a14f578e rs/name-rev-fix-free-after-use later to maint).
+
+ * "git show <commit1> <commit2>... -- <pathspec>" lost the pathspec
+ when showing the second and subsequent commits, which has been
+ corrected.
+ (merge 5cdb38458e jc/show-pathspec-fix later to maint).
+
+ * "git fast-export -- <pathspec>" lost the pathspec when showing the
+ second and subsequent commits, which has been corrected.
+ (merge d1c25272f5 rs/fast-export-pathspec-fix later to maint).
+
+ * "git format-patch <args> -- <pathspec>" lost the pathspec when
+ showing the second and subsequent commits, which has been
+ corrected.
+ (merge 91f8f7e46f rs/format-patch-pathspec-fix later to maint).
+
+ * "git clone --origin X" leaked piece of memory that held value read
+ from the clone.defaultRemoteName configuration variable, which has
+ been plugged.
+ (merge 6dfadc8981 jc/clone-remote-name-leak-fix later to maint).
+
+ * Get rid of a bogus and over-eager coccinelle rule.
+ (merge 08bdd3a185 jc/cocci-xstrdup-or-null-fix later to maint).
+
+ * The path taken by "git multi-pack-index" command from the end user
+ was compared with path internally prepared by the tool withut first
+ normalizing, which lead to duplicated paths not being noticed,
+ which has been corrected.
+ (merge 11f9e8de3d ds/midx-normalize-pathname-before-comparison later to maint).
+
+ * Correct choices of C compilers used in various CI jobs.
+ (merge 3506cae04f ab/cc-package-fixes later to maint).
+
+ * Various cleanups to "git p4".
+ (merge 4ff0108d9e jh/p4-various-fixups later to maint).
+
+ * The progress meter of "git blame" was showing incorrect numbers
+ when processing only parts of the file.
+ (merge e5f5d7d42e ea/progress-partial-blame later to maint).
+
+ * "git rebase --keep-base <upstream> <branch-to-rebase>" computed the
+ commit to rebase onto incorrectly, which has been corrected.
+ (merge 9e5ebe9668 ah/rebase-keep-base-fix later to maint).
+
+ * Fix a leak of FILE * in an error codepath.
+ (merge c0befa0c03 kt/commit-graph-plug-fp-leak-on-error later to maint).
+
+ * Avoid problems from interaction between malloc_check and address
+ sanitizer.
+ (merge 067109a5e7 pw/test-malloc-with-sanitize-address later to maint).
+
+ * The commit summary shown after making a commit is matched to what
+ is given in "git status" not to use the break-rewrite heuristics.
+ (merge 84792322ed rs/commit-summary-wo-break-rewrite later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+ (merge e6b2582da3 cm/reftable-0-length-memset later to maint).
+ (merge 0b75e5bf22 ab/misc-cleanup later to maint).
+ (merge 52e1ab8a76 ea/rebase-code-simplify later to maint).
diff --git a/Documentation/config/mergetool.txt b/Documentation/config/mergetool.txt
index cafbbef..90b3809 100644
--- a/Documentation/config/mergetool.txt
+++ b/Documentation/config/mergetool.txt
@@ -45,6 +45,15 @@
value of `false` avoids using `--auto-merge` altogether, and is the
default value.
+mergetool.vimdiff.layout::
+ The vimdiff backend uses this variable to control how its split
+ windows look like. Applies even if you are using Neovim (`nvim`) or
+ gVim (`gvim`) as the merge tool. See BACKEND SPECIFIC HINTS section
+ifndef::git-mergetool[]
+ in linkgit:git-mergetool[1].
+endif::[]
+ for details.
+
mergetool.hideResolved::
During a merge Git will automatically resolve as many conflicts as
possible and write the 'MERGED' file containing conflict markers around
diff --git a/Documentation/config/safe.txt b/Documentation/config/safe.txt
index 6d764fe..ae0e2e3 100644
--- a/Documentation/config/safe.txt
+++ b/Documentation/config/safe.txt
@@ -13,8 +13,8 @@
`safe.directory` entry with an empty value.
+
This config setting is only respected when specified in a system or global
-config, not when it is specified in a repository config or via the command
-line option `-c safe.directory=<path>`.
+config, not when it is specified in a repository config, via the command
+line option `-c safe.directory=<path>`, or in environment variables.
+
The value of this setting is interpolated, i.e. `~/<path>` expands to a
path relative to the home directory and `%(prefix)/<path>` expands to a
diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt
index e587c77..f784027 100644
--- a/Documentation/git-mergetool.txt
+++ b/Documentation/git-mergetool.txt
@@ -101,6 +101,7 @@
CONFIGURATION
-------------
+:git-mergetool: 1
include::config/mergetool.txt[]
TEMPORARY FILES
@@ -113,6 +114,13 @@
causes `git mergetool` to automatically remove the backup as files
are successfully merged.
+BACKEND SPECIFIC HINTS
+----------------------
+
+vimdiff
+~~~~~~~
+include::mergetools/vimdiff.txt[]
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 9da4647..262fb01 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -215,9 +215,10 @@
--keep-base::
Set the starting point at which to create the new commits to the
- merge base of <upstream> <branch>. Running
+ merge base of <upstream> and <branch>. Running
'git rebase --keep-base <upstream> <branch>' is equivalent to
- running 'git rebase --onto <upstream>... <upstream>'.
+ running
+ 'git rebase --onto <upstream>...<branch> <upstream> <branch>'.
+
This option is useful in the case where one is developing a feature on
top of an upstream branch. While the feature is being worked on, the
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 13f83a2..302607a 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -9,7 +9,7 @@
SYNOPSIS
--------
[verse]
-'git' [--version] [--help] [-C <path>] [-c <name>=<value>]
+'git' [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
@@ -39,6 +39,7 @@
OPTIONS
-------
+-v::
--version::
Prints the Git suite version that the 'git' program came from.
+
@@ -46,6 +47,7 @@
the same options as the linkgit:git-version[1] command. If `--help` is
also given, it takes precedence over `--version`.
+-h::
--help::
Prints the synopsis and a list of the most commonly used
commands. If the option `--all` or `-a` is given then all
diff --git a/Documentation/mergetools/vimdiff.txt b/Documentation/mergetools/vimdiff.txt
new file mode 100644
index 0000000..2d631e9
--- /dev/null
+++ b/Documentation/mergetools/vimdiff.txt
@@ -0,0 +1,194 @@
+Description
+^^^^^^^^^^^
+
+When specifying `--tool=vimdiff` in `git mergetool` Git will open Vim with a 4
+windows layout distributed in the following way:
+....
+------------------------------------------
+| | | |
+| LOCAL | BASE | REMOTE |
+| | | |
+------------------------------------------
+| |
+| MERGED |
+| |
+------------------------------------------
+....
+`LOCAL`, `BASE` and `REMOTE` are read-only buffers showing the contents of the
+conflicting file in specific commits ("commit you are merging into", "common
+ancestor commit" and "commit you are merging from" respectively)
+
+`MERGED` is a writable buffer where you have to resolve the conflicts (using the
+other read-only buffers as a reference). Once you are done, save and exit Vim as
+usual (`:wq`) or, if you want to abort, exit using `:cq`.
+
+Layout configuration
+^^^^^^^^^^^^^^^^^^^^
+
+You can change the windows layout used by Vim by setting configuration variable
+`mergetool.vimdiff.layout` which accepts a string where the following separators
+have special meaning:
+
+ - `+` is used to "open a new tab"
+ - `,` is used to "open a new vertical split"
+ - `/` is used to "open a new horizontal split"
+ - `@` is used to indicate which is the file containing the final version after
+ solving the conflicts. If not present, `MERGED` will be used by default.
+
+The precedence of the operators is this one (you can use parentheses to change
+it):
+
+ `@` > `+` > `/` > `,`
+
+Let's see some examples to understand how it works:
+
+* `layout = "(LOCAL,BASE,REMOTE)/MERGED"`
++
+--
+This is exactly the same as the default layout we have already seen.
+
+Note that `/` has precedence over `,` and thus the parenthesis are not
+needed in this case. The next layout definition is equivalent:
+
+ layout = "LOCAL,BASE,REMOTE / MERGED"
+--
+* `layout = "LOCAL,MERGED,REMOTE"`
++
+--
+If, for some reason, we are not interested in the `BASE` buffer.
+....
+------------------------------------------
+| | | |
+| | | |
+| LOCAL | MERGED | REMOTE |
+| | | |
+| | | |
+------------------------------------------
+....
+--
+* `layout = "MERGED"`
++
+--
+Only the `MERGED` buffer will be shown. Note, however, that all the other
+ones are still loaded in vim, and you can access them with the "buffers"
+command.
+....
+------------------------------------------
+| |
+| |
+| MERGED |
+| |
+| |
+------------------------------------------
+....
+--
+* `layout = "@LOCAL,REMOTE"`
++
+--
+When `MERGED` is not present in the layout, you must "mark" one of the
+buffers with an asterisk. That will become the buffer you need to edit and
+save after resolving the conflicts.
+....
+------------------------------------------
+| | |
+| | |
+| | |
+| LOCAL | REMOTE |
+| | |
+| | |
+| | |
+------------------------------------------
+....
+--
+* `layout = "LOCAL,BASE,REMOTE / MERGED + BASE,LOCAL + BASE,REMOTE"`
++
+--
+Three tabs will open: the first one is a copy of the default layout, while
+the other two only show the differences between (`BASE` and `LOCAL`) and
+(`BASE` and `REMOTE`) respectively.
+....
+------------------------------------------
+| <TAB #1> | TAB #2 | TAB #3 | |
+------------------------------------------
+| | | |
+| LOCAL | BASE | REMOTE |
+| | | |
+------------------------------------------
+| |
+| MERGED |
+| |
+------------------------------------------
+....
+....
+------------------------------------------
+| TAB #1 | <TAB #2> | TAB #3 | |
+------------------------------------------
+| | |
+| | |
+| | |
+| BASE | LOCAL |
+| | |
+| | |
+| | |
+------------------------------------------
+....
+....
+------------------------------------------
+| TAB #1 | TAB #2 | <TAB #3> | |
+------------------------------------------
+| | |
+| | |
+| | |
+| BASE | REMOTE |
+| | |
+| | |
+| | |
+------------------------------------------
+....
+--
+* `layout = "LOCAL,BASE,REMOTE / MERGED + BASE,LOCAL + BASE,REMOTE + (LOCAL/BASE/REMOTE),MERGED"`
++
+--
+Same as the previous example, but adds a fourth tab with the same
+information as the first tab, with a different layout.
+....
+---------------------------------------------
+| TAB #1 | TAB #2 | TAB #3 | <TAB #4> |
+---------------------------------------------
+| LOCAL | |
+|---------------------| |
+| BASE | MERGED |
+|---------------------| |
+| REMOTE | |
+---------------------------------------------
+....
+Note how in the third tab definition we need to use parenthesis to make `,`
+have precedence over `/`.
+--
+
+Variants
+^^^^^^^^
+
+Instead of `--tool=vimdiff`, you can also use one of these other variants:
+
+ * `--tool=gvimdiff`, to open gVim instead of Vim.
+
+ * `--tool=nvimdiff`, to open Neovim instead of Vim.
+
+When using these variants, in order to specify a custom layout you will have to
+set configuration variables `mergetool.gvimdiff.layout` and
+`mergetool.nvimdiff.layout` instead of `mergetool.vimdiff.layout`
+
+In addition, for backwards compatibility with previous Git versions, you can
+also append `1`, `2` or `3` to either `vimdiff` or any of the variants (ex:
+`vimdiff3`, `nvimdiff1`, etc...) to use a predefined layout.
+In other words, using `--tool=[g,n,]vimdiffx` is the same as using
+`--tool=[g,n,]vimdiff` and setting configuration variable
+`mergetool.[g,n,]vimdiff.layout` to...
+
+ * `x=1`: `"@LOCAL, REMOTE"`
+ * `x=2`: `"LOCAL, MERGED, REMOTE"`
+ * `x=3`: `"MERGED"`
+
+Example: using `--tool=gvimdiff2` will open `gvim` with three columns (LOCAL,
+MERGED and REMOTE).
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index fd4f4e2..195e74e 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -25,6 +25,11 @@
--after=<date>::
Show commits more recent than a specific date.
+--since-as-filter=<date>::
+ Show all commits more recent than a specific date. This visits
+ all commits in the range, rather than stopping at the first commit which
+ is older than a specific date.
+
--until=<date>::
--before=<date>::
Show commits older than a specific date.
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index fdcebd2..1d66769 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.36.0
+DEF_VER=v2.36.GIT
LF='
'
diff --git a/Makefile b/Makefile
index f8bccfa..61aadf3 100644
--- a/Makefile
+++ b/Makefile
@@ -1267,8 +1267,9 @@
SPARSE_FLAGS ?= -std=gnu99
SP_EXTRA_FLAGS = -Wno-universal-initializer
-# For informing GIT-BUILD-OPTIONS of the SANITIZE=leak target
+# For informing GIT-BUILD-OPTIONS of the SANITIZE=leak,address targets
SANITIZE_LEAK =
+SANITIZE_ADDRESS =
# For the 'coccicheck' target; setting SPATCH_BATCH_SIZE higher will
# usually result in less CPU usage at the cost of higher peak memory.
@@ -1314,6 +1315,7 @@
endif
ifneq ($(filter address,$(SANITIZERS)),)
NO_REGEX = NeededForASAN
+SANITIZE_ADDRESS = YesCompiledWithIt
endif
endif
@@ -2862,6 +2864,7 @@
@echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
@echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+
@echo SANITIZE_LEAK=\''$(subst ','\'',$(subst ','\'',$(SANITIZE_LEAK)))'\' >>$@+
+ @echo SANITIZE_ADDRESS=\''$(subst ','\'',$(subst ','\'',$(SANITIZE_ADDRESS)))'\' >>$@+
@echo X=\'$(X)\' >>$@+
ifdef FSMONITOR_DAEMON_BACKEND
@echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+
diff --git a/RelNotes b/RelNotes
index 8105226..51144b6 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.36.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.37.0.txt
\ No newline at end of file
diff --git a/alloc.c b/alloc.c
index 957a0af..27f697e 100644
--- a/alloc.c
+++ b/alloc.c
@@ -27,7 +27,6 @@ union any_object {
};
struct alloc_state {
- int count; /* total number of nodes allocated */
int nr; /* number of nodes left in current allocation */
void *p; /* first free node in current allocation */
@@ -63,7 +62,6 @@ static inline void *alloc_node(struct alloc_state *s, size_t node_size)
s->slabs[s->slab_nr++] = s->p;
}
s->nr--;
- s->count++;
ret = s->p;
s->p = (char *)s->p + node_size;
memset(ret, 0, node_size);
@@ -122,22 +120,3 @@ void *alloc_commit_node(struct repository *r)
init_commit_node(c);
return c;
}
-
-static void report(const char *name, unsigned int count, size_t size)
-{
- fprintf(stderr, "%10s: %8u (%"PRIuMAX" kB)\n",
- name, count, (uintmax_t) size);
-}
-
-#define REPORT(name, type) \
- report(#name, r->parsed_objects->name##_state->count, \
- r->parsed_objects->name##_state->count * sizeof(type) >> 10)
-
-void alloc_report(struct repository *r)
-{
- REPORT(blob, struct blob);
- REPORT(tree, struct tree);
- REPORT(commit, struct commit);
- REPORT(tag, struct tag);
- REPORT(object, union any_object);
-}
diff --git a/alloc.h b/alloc.h
index 371d388..3f4a0ad 100644
--- a/alloc.h
+++ b/alloc.h
@@ -13,7 +13,6 @@ void init_commit_node(struct commit *c);
void *alloc_commit_node(struct repository *r);
void *alloc_tag_node(struct repository *r);
void *alloc_object_node(struct repository *r);
-void alloc_report(struct repository *r);
struct alloc_state *allocate_alloc_state(void);
void clear_alloc_state(struct alloc_state *s);
diff --git a/builtin/apply.c b/builtin/apply.c
index 3f099b9..555219d 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -1,7 +1,6 @@
#include "cache.h"
#include "builtin.h"
#include "parse-options.h"
-#include "lockfile.h"
#include "apply.h"
static const char * const apply_usage[] = {
diff --git a/builtin/blame.c b/builtin/blame.c
index 8d15b68..e33372c 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -898,6 +898,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
unsigned int range_i;
long anchor;
const int hexsz = the_hash_algo->hexsz;
+ long num_lines = 0;
setup_default_color_by_age();
git_config(git_blame_config, &output_option);
@@ -1129,7 +1130,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
for (range_i = ranges.nr; range_i > 0; --range_i) {
const struct range *r = &ranges.ranges[range_i - 1];
ent = blame_entry_prepend(ent, r->start, r->end, o);
+ num_lines += (r->end - r->start);
}
+ if (!num_lines)
+ num_lines = sb.num_lines;
o->suspects = ent;
prio_queue_put(&sb.commits, o->commit);
@@ -1158,7 +1162,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
sb.found_guilty_entry = &found_guilty_entry;
sb.found_guilty_entry_data = π
if (show_progress)
- pi.progress = start_delayed_progress(_("Blaming lines"), sb.num_lines);
+ pi.progress = start_delayed_progress(_("Blaming lines"), num_lines);
assign_blame(&sb, opt);
diff --git a/builtin/clone.c b/builtin/clone.c
index 5231656..194d50f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1106,8 +1106,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
* apply the remote name provided by --origin only after this second
* call to git_config, to ensure it overrides all config-based values.
*/
- if (option_origin != NULL)
+ if (option_origin != NULL) {
+ free(remote_name);
remote_name = xstrdup(option_origin);
+ }
if (remote_name == NULL)
remote_name = xstrdup("origin");
diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c
index 0e0ac1f..116097a 100644
--- a/builtin/diff-tree.c
+++ b/builtin/diff-tree.c
@@ -195,6 +195,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
int saved_dcctc = 0;
opt->diffopt.rotate_to_strict = 0;
+ opt->diffopt.no_free = 1;
if (opt->diffopt.detect_rename) {
if (!the_index.cache)
repo_read_index(the_repository);
@@ -217,6 +218,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
}
opt->diffopt.degraded_cc_to_c = saved_dcctc;
opt->diffopt.needed_rename_limit = saved_nrl;
+ opt->diffopt.no_free = 0;
+ diff_free(&opt->diffopt);
}
return diff_result_code(&opt->diffopt, 0);
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index a7d7269..1355b5a 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -1261,6 +1261,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
revs.diffopt.format_callback = show_filemodify;
revs.diffopt.format_callback_data = &paths_of_changed_objects;
revs.diffopt.flags.recursive = 1;
+ revs.diffopt.no_free = 1;
while ((commit = get_revision(&revs)))
handle_commit(commit, &revs, &paths_of_changed_objects);
diff --git a/builtin/log.c b/builtin/log.c
index c211d66..3ac479b 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -417,7 +417,7 @@ static void finish_early_output(struct rev_info *rev)
show_early_header(rev, "done", n);
}
-static int cmd_log_walk(struct rev_info *rev)
+static int cmd_log_walk_no_free(struct rev_info *rev)
{
struct commit *commit;
int saved_nrl = 0;
@@ -444,7 +444,6 @@ static int cmd_log_walk(struct rev_info *rev)
* and HAS_CHANGES being accumulated in rev->diffopt, so be careful to
* retain that state information if replacing rev->diffopt in this loop
*/
- rev->diffopt.no_free = 1;
while ((commit = get_revision(rev)) != NULL) {
if (!log_tree_commit(rev, commit) && rev->max_count >= 0)
/*
@@ -469,8 +468,6 @@ static int cmd_log_walk(struct rev_info *rev)
}
rev->diffopt.degraded_cc_to_c = saved_dcctc;
rev->diffopt.needed_rename_limit = saved_nrl;
- rev->diffopt.no_free = 0;
- diff_free(&rev->diffopt);
if (rev->remerge_diff) {
tmp_objdir_destroy(rev->remerge_objdir);
@@ -484,6 +481,17 @@ static int cmd_log_walk(struct rev_info *rev)
return diff_result_code(&rev->diffopt, 0);
}
+static int cmd_log_walk(struct rev_info *rev)
+{
+ int retval;
+
+ rev->diffopt.no_free = 1;
+ retval = cmd_log_walk_no_free(rev);
+ rev->diffopt.no_free = 0;
+ diff_free(&rev->diffopt);
+ return retval;
+}
+
static int git_log_config(const char *var, const char *value, void *cb)
{
const char *slot_name;
@@ -680,6 +688,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
count = rev.pending.nr;
objects = rev.pending.objects;
+ rev.diffopt.no_free = 1;
for (i = 0; i < count && !ret; i++) {
struct object *o = objects[i].item;
const char *name = objects[i].name;
@@ -725,12 +734,16 @@ int cmd_show(int argc, const char **argv, const char *prefix)
rev.pending.nr = rev.pending.alloc = 0;
rev.pending.objects = NULL;
add_object_array(o, name, &rev.pending);
- ret = cmd_log_walk(&rev);
+ ret = cmd_log_walk_no_free(&rev);
break;
default:
ret = error(_("unknown type: %d"), o->type);
}
}
+
+ rev.diffopt.no_free = 0;
+ diff_free(&rev.diffopt);
+
free(objects);
return ret;
}
@@ -1883,6 +1896,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.diff = 1;
rev.max_parents = 1;
rev.diffopt.flags.recursive = 1;
+ rev.diffopt.no_free = 1;
rev.subject_prefix = fmt_patch_subject_prefix;
memset(&s_r_opt, 0, sizeof(s_r_opt));
s_r_opt.def = "HEAD";
@@ -2008,13 +2022,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (use_stdout) {
setup_pager();
- } else if (rev.diffopt.close_file) {
- /*
- * The diff code parsed --output; it has already opened the
- * file, but we must instruct it not to close after each diff.
- */
- rev.diffopt.no_free = 1;
- } else {
+ } else if (!rev.diffopt.close_file) {
int saved;
if (!output_directory)
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index 4480ba3..5edbb7f 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -44,7 +44,7 @@ static char const * const builtin_multi_pack_index_usage[] = {
};
static struct opts_multi_pack_index {
- const char *object_dir;
+ char *object_dir;
const char *preferred_pack;
const char *refs_snapshot;
unsigned long batch_size;
@@ -52,9 +52,23 @@ static struct opts_multi_pack_index {
int stdin_packs;
} opts;
+
+static int parse_object_dir(const struct option *opt, const char *arg,
+ int unset)
+{
+ free(opts.object_dir);
+ if (unset)
+ opts.object_dir = xstrdup(get_object_directory());
+ else
+ opts.object_dir = real_pathdup(arg, 1);
+ return 0;
+}
+
static struct option common_opts[] = {
- OPT_FILENAME(0, "object-dir", &opts.object_dir,
- N_("object directory containing set of packfile and pack-index pairs")),
+ OPT_CALLBACK(0, "object-dir", &opts.object_dir,
+ N_("directory"),
+ N_("object directory containing set of packfile and pack-index pairs"),
+ parse_object_dir),
OPT_END(),
};
@@ -232,31 +246,40 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv)
int cmd_multi_pack_index(int argc, const char **argv,
const char *prefix)
{
+ int res;
struct option *builtin_multi_pack_index_options = common_opts;
git_config(git_default_config, NULL);
+ if (the_repository &&
+ the_repository->objects &&
+ the_repository->objects->odb)
+ opts.object_dir = xstrdup(the_repository->objects->odb->path);
+
argc = parse_options(argc, argv, prefix,
builtin_multi_pack_index_options,
builtin_multi_pack_index_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
- if (!opts.object_dir)
- opts.object_dir = get_object_directory();
-
if (!argc)
goto usage;
if (!strcmp(argv[0], "repack"))
- return cmd_multi_pack_index_repack(argc, argv);
+ res = cmd_multi_pack_index_repack(argc, argv);
else if (!strcmp(argv[0], "write"))
- return cmd_multi_pack_index_write(argc, argv);
+ res = cmd_multi_pack_index_write(argc, argv);
else if (!strcmp(argv[0], "verify"))
- return cmd_multi_pack_index_verify(argc, argv);
+ res = cmd_multi_pack_index_verify(argc, argv);
else if (!strcmp(argv[0], "expire"))
- return cmd_multi_pack_index_expire(argc, argv);
+ res = cmd_multi_pack_index_expire(argc, argv);
+ else {
+ error(_("unrecognized subcommand: %s"), argv[0]);
+ goto usage;
+ }
- error(_("unrecognized subcommand: %s"), argv[0]);
+ free(opts.object_dir);
+ return res;
+
usage:
usage_with_options(builtin_multi_pack_index_usage,
builtin_multi_pack_index_options);
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index c59b569..02ea9d1 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -18,7 +18,7 @@
#define CUTOFF_DATE_SLOP 86400
struct rev_name {
- char *tip_name;
+ const char *tip_name;
timestamp_t taggerdate;
int generation;
int distance;
@@ -84,7 +84,7 @@ static int commit_is_before_cutoff(struct commit *commit)
static int is_valid_rev_name(const struct rev_name *name)
{
- return name && (name->generation || name->tip_name);
+ return name && name->tip_name;
}
static struct rev_name *get_commit_rev_name(const struct commit *commit)
@@ -146,20 +146,9 @@ static struct rev_name *create_or_update_name(struct commit *commit,
{
struct rev_name *name = commit_rev_name_at(&rev_names, commit);
- if (is_valid_rev_name(name)) {
- if (!is_better_name(name, taggerdate, generation, distance, from_tag))
- return NULL;
-
- /*
- * This string might still be shared with ancestors
- * (generation > 0). We can release it here regardless,
- * because the new name that has just won will be better
- * for them as well, so name_rev() will replace these
- * stale pointers when it processes the parents.
- */
- if (!name->generation)
- free(name->tip_name);
- }
+ if (is_valid_rev_name(name) &&
+ !is_better_name(name, taggerdate, generation, distance, from_tag))
+ return NULL;
name->taggerdate = taggerdate;
name->generation = generation;
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 27fde7b..7ab50cd 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1187,11 +1187,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
} else {
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/interactive", merge_dir());
- if(file_exists(buf.buf)) {
- options.type = REBASE_MERGE;
+ options.type = REBASE_MERGE;
+ if (file_exists(buf.buf))
options.flags |= REBASE_INTERACTIVE_EXPLICIT;
- } else
- options.type = REBASE_MERGE;
}
options.state_dir = merge_dir();
}
@@ -1583,33 +1581,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
options.upstream_arg = "--root";
}
- /* Make sure the branch to rebase onto is valid. */
- if (keep_base) {
- strbuf_reset(&buf);
- strbuf_addstr(&buf, options.upstream_name);
- strbuf_addstr(&buf, "...");
- options.onto_name = xstrdup(buf.buf);
- } else if (!options.onto_name)
- options.onto_name = options.upstream_name;
- if (strstr(options.onto_name, "...")) {
- if (get_oid_mb(options.onto_name, &merge_base) < 0) {
- if (keep_base)
- die(_("'%s': need exactly one merge base with branch"),
- options.upstream_name);
- else
- die(_("'%s': need exactly one merge base"),
- options.onto_name);
- }
- options.onto = lookup_commit_or_die(&merge_base,
- options.onto_name);
- } else {
- options.onto =
- lookup_commit_reference_by_name(options.onto_name);
- if (!options.onto)
- die(_("Does not point to a valid commit '%s'"),
- options.onto_name);
- }
-
/*
* If the branch to rebase is given, that is the branch we will rebase
* branch_name -- branch/commit being rebased, or
@@ -1659,6 +1630,34 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
} else
BUG("unexpected number of arguments left to parse");
+ /* Make sure the branch to rebase onto is valid. */
+ if (keep_base) {
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, options.upstream_name);
+ strbuf_addstr(&buf, "...");
+ strbuf_addstr(&buf, branch_name);
+ options.onto_name = xstrdup(buf.buf);
+ } else if (!options.onto_name)
+ options.onto_name = options.upstream_name;
+ if (strstr(options.onto_name, "...")) {
+ if (get_oid_mb(options.onto_name, &merge_base) < 0) {
+ if (keep_base)
+ die(_("'%s': need exactly one merge base with branch"),
+ options.upstream_name);
+ else
+ die(_("'%s': need exactly one merge base"),
+ options.onto_name);
+ }
+ options.onto = lookup_commit_or_die(&merge_base,
+ options.onto_name);
+ } else {
+ options.onto =
+ lookup_commit_reference_by_name(options.onto_name);
+ if (!options.onto)
+ die(_("Does not point to a valid commit '%s'"),
+ options.onto_name);
+ }
+
if (options.fork_point > 0) {
struct commit *head =
lookup_commit_reference(the_repository,
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 2c87ef9..1a8e5d0 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2026,7 +2026,6 @@ struct update_data {
.references = STRING_LIST_INIT_DUP, \
.single_branch = -1, \
.max_jobs = 1, \
- .warn_if_uninitialized = 1, \
}
static void next_submodule_warn_missing(struct submodule_update_clone *suc,
diff --git a/cache.h b/cache.h
index 6226f6a..595582b 100644
--- a/cache.h
+++ b/cache.h
@@ -566,7 +566,7 @@ extern char *git_work_tree_cfg;
int is_inside_work_tree(void);
const char *get_git_dir(void);
const char *get_git_common_dir(void);
-char *get_object_directory(void);
+const char *get_object_directory(void);
char *get_index_file(void);
char *get_graft_file(struct repository *r);
void set_git_dir(const char *path, int make_realpath);
diff --git a/ci/lib.sh b/ci/lib.sh
index cbc2f8f..86e37da 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -122,7 +122,7 @@
test macos != "$CI_OS_NAME" || CI_OS_NAME=osx
CI_REPO_SLUG="$GITHUB_REPOSITORY"
CI_JOB_ID="$GITHUB_RUN_ID"
- CC="${CC:-gcc}"
+ CC="${CC_PACKAGE:-${CC:-gcc}}"
DONT_SKIP_TAGS=t
cache_dir="$HOME/none"
diff --git a/commit-graph.c b/commit-graph.c
index 441b360..06107be 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -523,10 +523,13 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,
stat_res = stat(chain_name, &st);
free(chain_name);
- if (!fp ||
- stat_res ||
- st.st_size <= the_hash_algo->hexsz)
+ if (!fp)
return NULL;
+ if (stat_res ||
+ st.st_size <= the_hash_algo->hexsz) {
+ fclose(fp);
+ return NULL;
+ }
count = st.st_size / (the_hash_algo->hexsz + 1);
CALLOC_ARRAY(oids, count);
diff --git a/configure.ac b/configure.ac
index 316a31d..7dcd048 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1189,9 +1189,6 @@
GIT_CONF_SUBST([HAVE_BSD_SYSCTL])
## Other checks.
-# Define USE_PIC if you need the main git objects to be built with -fPIC
-# in order to build and link perl/Git.so. x86-64 seems to need this.
-#
# Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
# Enable it on Windows. By default, symrefs are still used.
#
diff --git a/contrib/coccinelle/the_repository.pending.cocci b/contrib/coccinelle/the_repository.pending.cocci
index 2ee702e..072ea0d 100644
--- a/contrib/coccinelle/the_repository.pending.cocci
+++ b/contrib/coccinelle/the_repository.pending.cocci
@@ -14,21 +14,6 @@
@@
expression E;
@@
-- has_sha1_file(
-+ repo_has_sha1_file(the_repository,
- E)
-
-@@
-expression E;
-expression F;
-@@
-- has_sha1_file_with_flags(
-+ repo_has_sha1_file_with_flags(the_repository,
- E)
-
-@@
-expression E;
-@@
- has_object_file(
+ repo_has_object_file(the_repository,
E)
diff --git a/contrib/coccinelle/xstrdup_or_null.cocci b/contrib/coccinelle/xstrdup_or_null.cocci
index 8e05d1c..9c1d293 100644
--- a/contrib/coccinelle/xstrdup_or_null.cocci
+++ b/contrib/coccinelle/xstrdup_or_null.cocci
@@ -1,13 +1,5 @@
@@
expression E;
-expression V;
-@@
-- if (E)
-- V = xstrdup(E);
-+ V = xstrdup_or_null(E);
-
-@@
-expression E;
@@
- xstrdup(absolute_path(E))
+ absolute_pathdup(E)
diff --git a/contrib/vscode/README.md b/contrib/vscode/README.md
index 8202d62..f383c95 100644
--- a/contrib/vscode/README.md
+++ b/contrib/vscode/README.md
@@ -6,7 +6,11 @@
[Windows](https://code.visualstudio.com/docs/setup/windows),
[macOS](https://code.visualstudio.com/docs/setup/mac) and
[Linux](https://code.visualstudio.com/docs/setup/linux). Among other languages,
-it has [support for C/C++ via an extension](https://github.com/Microsoft/vscode-cpptools).
+it has [support for C/C++ via an extension](https://github.com/Microsoft/vscode-cpptools) with
+[debugging support](https://code.visualstudio.com/docs/editor/debugging)
+
+To get help about "how to personalize your settings" read:
+[How to set up your settings](https://code.visualstudio.com/docs/getstarted/settings)
To start developing Git with VS Code, simply run the Unix shell script called
`init.sh` in this directory, which creates the configuration files in
diff --git a/contrib/vscode/init.sh b/contrib/vscode/init.sh
index 27de949..f139fd8 100755
--- a/contrib/vscode/init.sh
+++ b/contrib/vscode/init.sh
@@ -271,7 +271,6 @@
"stopAtEntry": false,
"cwd": "\${workspaceFolder}",
"environment": [],
- "externalConsole": true,
"MIMode": "gdb",
"miDebuggerPath": "$GDBPATH",
"setupCommands": [
diff --git a/convert.c b/convert.c
index 8e39731..4d15372 100644
--- a/convert.c
+++ b/convert.c
@@ -195,9 +195,9 @@ static void check_global_conv_flags_eol(const char *path,
if (conv_flags & CONV_EOL_RNDTRP_DIE)
die(_("CRLF would be replaced by LF in %s"), path);
else if (conv_flags & CONV_EOL_RNDTRP_WARN)
- warning(_("CRLF will be replaced by LF in %s.\n"
- "The file will have its original line"
- " endings in your working directory"), path);
+ warning(_("in the working copy of '%s', CRLF will be"
+ " replaced by LF the next time Git touches"
+ " it"), path);
} else if (old_stats->lonelf && !new_stats->lonelf ) {
/*
* CRLFs would be added by checkout
@@ -205,9 +205,9 @@ static void check_global_conv_flags_eol(const char *path,
if (conv_flags & CONV_EOL_RNDTRP_DIE)
die(_("LF would be replaced by CRLF in %s"), path);
else if (conv_flags & CONV_EOL_RNDTRP_WARN)
- warning(_("LF will be replaced by CRLF in %s.\n"
- "The file will have its original line"
- " endings in your working directory"), path);
+ warning(_("in the working copy of '%s', LF will be"
+ " replaced by CRLF the next time Git touches"
+ " it"), path);
}
}
diff --git a/diff.c b/diff.c
index ef71599..e71cf75 100644
--- a/diff.c
+++ b/diff.c
@@ -4136,18 +4136,13 @@ static void prep_temp_blob(struct index_state *istate,
int mode)
{
struct strbuf buf = STRBUF_INIT;
- struct strbuf tempfile = STRBUF_INIT;
char *path_dup = xstrdup(path);
const char *base = basename(path_dup);
struct checkout_metadata meta;
init_checkout_metadata(&meta, NULL, NULL, oid);
- /* Generate "XXXXXX_basename.ext" */
- strbuf_addstr(&tempfile, "XXXXXX_");
- strbuf_addstr(&tempfile, base);
-
- temp->tempfile = mks_tempfile_ts(tempfile.buf, strlen(base) + 1);
+ temp->tempfile = mks_tempfile_dt("git-blob-XXXXXX", base);
if (!temp->tempfile)
die_errno("unable to create temp-file");
if (convert_to_working_tree(istate, path,
@@ -4162,7 +4157,6 @@ static void prep_temp_blob(struct index_state *istate,
oid_to_hex_r(temp->hex, oid);
xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode);
strbuf_release(&buf);
- strbuf_release(&tempfile);
free(path_dup);
}
diff --git a/dir.c b/dir.c
index f2b0f24..26c4d14 100644
--- a/dir.c
+++ b/dir.c
@@ -2747,13 +2747,33 @@ static void set_untracked_ident(struct untracked_cache *uc)
strbuf_addch(&uc->ident, 0);
}
-static void new_untracked_cache(struct index_state *istate)
+static unsigned new_untracked_cache_flags(struct index_state *istate)
+{
+ struct repository *repo = istate->repo;
+ char *val;
+
+ /*
+ * This logic is coordinated with the setting of these flags in
+ * wt-status.c#wt_status_collect_untracked(), and the evaluation
+ * of the config setting in commit.c#git_status_config()
+ */
+ if (!repo_config_get_string(repo, "status.showuntrackedfiles", &val) &&
+ !strcmp(val, "all"))
+ return 0;
+
+ /*
+ * The default, if "all" is not set, is "normal" - leading us here.
+ * If the value is "none" then it really doesn't matter.
+ */
+ return DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
+}
+
+static void new_untracked_cache(struct index_state *istate, int flags)
{
struct untracked_cache *uc = xcalloc(1, sizeof(*uc));
strbuf_init(&uc->ident, 100);
uc->exclude_per_dir = ".gitignore";
- /* should be the same flags used by git-status */
- uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
+ uc->dir_flags = flags >= 0 ? flags : new_untracked_cache_flags(istate);
set_untracked_ident(uc);
istate->untracked = uc;
istate->cache_changed |= UNTRACKED_CHANGED;
@@ -2762,11 +2782,11 @@ static void new_untracked_cache(struct index_state *istate)
void add_untracked_cache(struct index_state *istate)
{
if (!istate->untracked) {
- new_untracked_cache(istate);
+ new_untracked_cache(istate, -1);
} else {
if (!ident_in_untracked(istate->untracked)) {
free_untracked_cache(istate->untracked);
- new_untracked_cache(istate);
+ new_untracked_cache(istate, -1);
}
}
}
@@ -2814,17 +2834,9 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
if (base_len || (pathspec && pathspec->nr))
return NULL;
- /* Different set of flags may produce different results */
- if (dir->flags != dir->untracked->dir_flags ||
- /*
- * See treat_directory(), case index_nonexistent. Without
- * this flag, we may need to also cache .git file content
- * for the resolve_gitlink_ref() call, which we don't.
- */
- !(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) ||
- /* We don't support collecting ignore files */
- (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
- DIR_COLLECT_IGNORED)))
+ /* We don't support collecting ignore files */
+ if (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
+ DIR_COLLECT_IGNORED))
return NULL;
/*
@@ -2847,6 +2859,50 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
return NULL;
}
+ /*
+ * If the untracked structure we received does not have the same flags
+ * as requested in this run, we're going to need to either discard the
+ * existing structure (and potentially later recreate), or bypass the
+ * untracked cache mechanism for this run.
+ */
+ if (dir->flags != dir->untracked->dir_flags) {
+ /*
+ * If the untracked structure we received does not have the same flags
+ * as configured, then we need to reset / create a new "untracked"
+ * structure to match the new config.
+ *
+ * Keeping the saved and used untracked cache consistent with the
+ * configuration provides an opportunity for frequent users of
+ * "git status -uall" to leverage the untracked cache by aligning their
+ * configuration - setting "status.showuntrackedfiles" to "all" or
+ * "normal" as appropriate.
+ *
+ * Previously using -uall (or setting "status.showuntrackedfiles" to
+ * "all") was incompatible with untracked cache and *consistently*
+ * caused surprisingly bad performance (with fscache and fsmonitor
+ * enabled) on Windows.
+ *
+ * IMPROVEMENT OPPORTUNITY: If we reworked the untracked cache storage
+ * to not be as bound up with the desired output in a given run,
+ * and instead iterated through and stored enough information to
+ * correctly serve both "modes", then users could get peak performance
+ * with or without '-uall' regardless of their
+ * "status.showuntrackedfiles" config.
+ */
+ if (dir->untracked->dir_flags != new_untracked_cache_flags(istate)) {
+ free_untracked_cache(istate->untracked);
+ new_untracked_cache(istate, dir->flags);
+ dir->untracked = istate->untracked;
+ }
+ else {
+ /*
+ * Current untracked cache data is consistent with config, but not
+ * usable in this request/run; just bypass untracked cache.
+ */
+ return NULL;
+ }
+ }
+
if (!dir->untracked->root) {
/* Untracked cache existed but is not initialized; fix that */
FLEX_ALLOC_STR(dir->untracked->root, name, "");
diff --git a/environment.c b/environment.c
index 5bff1b3..b3296ce 100644
--- a/environment.c
+++ b/environment.c
@@ -273,7 +273,7 @@ const char *get_git_work_tree(void)
return the_repository->worktree;
}
-char *get_object_directory(void)
+const char *get_object_directory(void)
{
if (!the_repository->objects->odb)
BUG("git environment hasn't been setup");
diff --git a/ewah/bitmap.c b/ewah/bitmap.c
index 38a47c4..87d5cc8 100644
--- a/ewah/bitmap.c
+++ b/ewah/bitmap.c
@@ -216,11 +216,6 @@ int bitmap_is_subset(struct bitmap *self, struct bitmap *other)
return 0;
}
-void bitmap_reset(struct bitmap *bitmap)
-{
- memset(bitmap->words, 0x0, bitmap->word_alloc * sizeof(eword_t));
-}
-
void bitmap_free(struct bitmap *bitmap)
{
if (bitmap == NULL)
diff --git a/ewah/ewok.h b/ewah/ewok.h
index 6692096..7eb8b9b 100644
--- a/ewah/ewok.h
+++ b/ewah/ewok.h
@@ -177,7 +177,6 @@ struct bitmap *bitmap_dup(const struct bitmap *src);
void bitmap_set(struct bitmap *self, size_t pos);
void bitmap_unset(struct bitmap *self, size_t pos);
int bitmap_get(struct bitmap *self, size_t pos);
-void bitmap_reset(struct bitmap *self);
void bitmap_free(struct bitmap *self);
int bitmap_equals(struct bitmap *self, struct bitmap *other);
int bitmap_is_subset(struct bitmap *self, struct bitmap *other);
diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh
index 542a6a7..9f99201 100644
--- a/git-mergetool--lib.sh
+++ b/git-mergetool--lib.sh
@@ -63,7 +63,7 @@
preamble=
fi
shown_any=yes
- printf "%s%s\n" "$per_line_prefix" "$toolname"
+ printf "%s%-15s %s\n" "$per_line_prefix" "$toolname" $(diff_mode && diff_cmd_help "$toolname" || merge_cmd_help "$toolname")
fi
done
@@ -162,10 +162,18 @@
return 1
}
+ diff_cmd_help () {
+ return 0
+ }
+
merge_cmd () {
return 1
}
+ merge_cmd_help () {
+ return 0
+ }
+
hide_resolved_enabled () {
return 0
}
diff --git a/git-p4.py b/git-p4.py
index a9b1f90..c47abb4 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -7,34 +7,51 @@
# 2007 Trolltech ASA
# License: MIT <http://www.opensource.org/licenses/mit-license.php>
#
-# pylint: disable=invalid-name,missing-docstring,too-many-arguments,broad-except
-# pylint: disable=no-self-use,wrong-import-position,consider-iterating-dictionary
-# pylint: disable=wrong-import-order,unused-import,too-few-public-methods
-# pylint: disable=too-many-lines,ungrouped-imports,fixme,too-many-locals
-# pylint: disable=line-too-long,bad-whitespace,superfluous-parens
-# pylint: disable=too-many-statements,too-many-instance-attributes
-# pylint: disable=too-many-branches,too-many-nested-blocks
+# pylint: disable=bad-whitespace
+# pylint: disable=broad-except
+# pylint: disable=consider-iterating-dictionary
+# pylint: disable=disable
+# pylint: disable=fixme
+# pylint: disable=invalid-name
+# pylint: disable=line-too-long
+# pylint: disable=missing-docstring
+# pylint: disable=no-self-use
+# pylint: disable=superfluous-parens
+# pylint: disable=too-few-public-methods
+# pylint: disable=too-many-arguments
+# pylint: disable=too-many-branches
+# pylint: disable=too-many-instance-attributes
+# pylint: disable=too-many-lines
+# pylint: disable=too-many-locals
+# pylint: disable=too-many-nested-blocks
+# pylint: disable=too-many-statements
+# pylint: disable=ungrouped-imports
+# pylint: disable=unused-import
+# pylint: disable=wrong-import-order
+# pylint: disable=wrong-import-position
#
+
import sys
if sys.version_info.major < 3 and sys.version_info.minor < 7:
sys.stderr.write("git-p4: requires Python 2.7 or later.\n")
sys.exit(1)
-import os
-import optparse
+
+import ctypes
+import errno
import functools
+import glob
import marshal
-import subprocess
-import tempfile
-import time
+import optparse
+import os
import platform
import re
import shutil
import stat
+import subprocess
+import tempfile
+import time
import zipfile
import zlib
-import ctypes
-import errno
-import glob
# On python2.7 where raw_input() and input() are both availble,
# we want raw_input's semantics, but aliased to input for python3
@@ -52,17 +69,18 @@
defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$'
# The block size is reduced automatically if required
-defaultBlockSize = 1<<20
+defaultBlockSize = 1 << 20
p4_access_checked = False
re_ko_keywords = re.compile(br'\$(Id|Header)(:[^$\n]+)?\$')
re_k_keywords = re.compile(br'\$(Id|Header|Author|Date|DateTime|Change|File|Revision)(:[^$\n]+)?\$')
+
def format_size_human_readable(num):
- """ Returns a number of units (typically bytes) formatted as a human-readable
- string.
- """
+ """Returns a number of units (typically bytes) formatted as a
+ human-readable string.
+ """
if num < 1024:
return '{:d} B'.format(num)
for unit in ["Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
@@ -71,18 +89,19 @@ def format_size_human_readable(num):
return "{:3.1f} {}B".format(num, unit)
return "{:.1f} YiB".format(num)
+
def p4_build_cmd(cmd):
"""Build a suitable p4 command line.
- This consolidates building and returning a p4 command line into one
- location. It means that hooking into the environment, or other configuration
- can be done more easily.
- """
+ This consolidates building and returning a p4 command line into one
+ location. It means that hooking into the environment, or other
+ configuration can be done more easily.
+ """
real_cmd = ["p4"]
user = gitConfig("git-p4.user")
if len(user) > 0:
- real_cmd += ["-u",user]
+ real_cmd += ["-u", user]
password = gitConfig("git-p4.password")
if len(password) > 0:
@@ -118,31 +137,31 @@ def p4_build_cmd(cmd):
return real_cmd
+
def git_dir(path):
- """ Return TRUE if the given path is a git directory (/path/to/dir/.git).
- This won't automatically add ".git" to a directory.
- """
+ """Return TRUE if the given path is a git directory (/path/to/dir/.git).
+ This won't automatically add ".git" to a directory.
+ """
d = read_pipe(["git", "--git-dir", path, "rev-parse", "--git-dir"], True).strip()
if not d or len(d) == 0:
return None
else:
return d
+
def chdir(path, is_client_path=False):
- """Do chdir to the given path, and set the PWD environment
- variable for use by P4. It does not look at getcwd() output.
- Since we're not using the shell, it is necessary to set the
- PWD environment variable explicitly.
+ """Do chdir to the given path, and set the PWD environment variable for use
+ by P4. It does not look at getcwd() output. Since we're not using the
+ shell, it is necessary to set the PWD environment variable explicitly.
- Normally, expand the path to force it to be absolute. This
- addresses the use of relative path names inside P4 settings,
- e.g. P4CONFIG=.p4config. P4 does not simply open the filename
- as given; it looks for .p4config using PWD.
+ Normally, expand the path to force it to be absolute. This addresses
+ the use of relative path names inside P4 settings, e.g.
+ P4CONFIG=.p4config. P4 does not simply open the filename as given; it
+ looks for .p4config using PWD.
- If is_client_path, the path was handed to us directly by p4,
- and may be a symbolic link. Do not call os.getcwd() in this
- case, because it will cause p4 to think that PWD is not inside
- the client path.
+ If is_client_path, the path was handed to us directly by p4, and may be
+ a symbolic link. Do not call os.getcwd() in this case, because it will
+ cause p4 to think that PWD is not inside the client path.
"""
os.chdir(path)
@@ -150,6 +169,7 @@ def chdir(path, is_client_path=False):
path = os.getcwd()
os.environ['PWD'] = path
+
def calcDiskFree():
"""Return free space in bytes on the disk of the given dirname."""
if platform.system() == 'Windows':
@@ -160,52 +180,60 @@ def calcDiskFree():
st = os.statvfs(os.getcwd())
return st.f_bavail * st.f_frsize
+
def die(msg):
- """ Terminate execution. Make sure that any running child processes have been wait()ed for before
- calling this.
- """
+ """Terminate execution. Make sure that any running child processes have
+ been wait()ed for before calling this.
+ """
if verbose:
raise Exception(msg)
else:
sys.stderr.write(msg + "\n")
sys.exit(1)
-def prompt(prompt_text):
- """ Prompt the user to choose one of the choices
- Choices are identified in the prompt_text by square brackets around
- a single letter option.
- """
+def prompt(prompt_text):
+ """Prompt the user to choose one of the choices.
+
+ Choices are identified in the prompt_text by square brackets around a
+ single letter option.
+ """
choices = set(m.group(1) for m in re.finditer(r"\[(.)\]", prompt_text))
while True:
sys.stderr.flush()
sys.stdout.write(prompt_text)
sys.stdout.flush()
- response=sys.stdin.readline().strip().lower()
+ response = sys.stdin.readline().strip().lower()
if not response:
continue
response = response[0]
if response in choices:
return response
+
# We need different encoding/decoding strategies for text data being passed
# around in pipes depending on python version
if bytes is not str:
# For python3, always encode and decode as appropriate
def decode_text_stream(s):
return s.decode() if isinstance(s, bytes) else s
+
def encode_text_stream(s):
return s.encode() if isinstance(s, str) else s
else:
# For python2.7, pass read strings as-is, but also allow writing unicode
def decode_text_stream(s):
return s
+
def encode_text_stream(s):
return s.encode('utf_8') if isinstance(s, unicode) else s
+
def decode_path(path):
- """Decode a given string (bytes or otherwise) using configured path encoding options
- """
+ """Decode a given string (bytes or otherwise) using configured path
+ encoding options.
+ """
+
encoding = gitConfig('git-p4.pathEncoding') or 'utf_8'
if bytes is not str:
return path.decode(encoding, errors='replace') if isinstance(path, bytes) else path
@@ -218,6 +246,7 @@ def decode_path(path):
print('Path with non-ASCII characters detected. Used {} to decode: {}'.format(encoding, path))
return path
+
def run_git_hook(cmd, param=[]):
"""Execute a hook if the hook exists."""
args = ['git', 'hook', 'run', '--ignore-missing', cmd]
@@ -227,6 +256,7 @@ def run_git_hook(cmd, param=[]):
args.append(p)
return subprocess.call(args) == 0
+
def write_pipe(c, stdin, *k, **kw):
if verbose:
sys.stderr.write('Writing pipe: {}\n'.format(' '.join(c)))
@@ -240,33 +270,35 @@ def write_pipe(c, stdin, *k, **kw):
return val
+
def p4_write_pipe(c, stdin, *k, **kw):
real_cmd = p4_build_cmd(c)
if bytes is not str and isinstance(stdin, str):
stdin = encode_text_stream(stdin)
return write_pipe(real_cmd, stdin, *k, **kw)
+
def read_pipe_full(c, *k, **kw):
- """ Read output from command. Returns a tuple
- of the return status, stdout text and stderr
- text.
- """
+ """Read output from command. Returns a tuple of the return status, stdout
+ text and stderr text.
+ """
if verbose:
sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c)))
p = subprocess.Popen(
c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, *k, **kw)
- (out, err) = p.communicate()
+ out, err = p.communicate()
return (p.returncode, out, decode_text_stream(err))
-def read_pipe(c, ignore_error=False, raw=False, *k, **kw):
- """ Read output from command. Returns the output text on
- success. On failure, terminates execution, unless
- ignore_error is True, when it returns an empty string.
- If raw is True, do not attempt to decode output text.
- """
- (retcode, out, err) = read_pipe_full(c, *k, **kw)
+def read_pipe(c, ignore_error=False, raw=False, *k, **kw):
+ """Read output from command. Returns the output text on success. On
+ failure, terminates execution, unless ignore_error is True, when it
+ returns an empty string.
+
+ If raw is True, do not attempt to decode output text.
+ """
+ retcode, out, err = read_pipe_full(c, *k, **kw)
if retcode != 0:
if ignore_error:
out = ""
@@ -276,20 +308,23 @@ def read_pipe(c, ignore_error=False, raw=False, *k, **kw):
out = decode_text_stream(out)
return out
+
def read_pipe_text(c, *k, **kw):
- """ Read output from a command with trailing whitespace stripped.
- On error, returns None.
- """
- (retcode, out, err) = read_pipe_full(c, *k, **kw)
+ """Read output from a command with trailing whitespace stripped. On error,
+ returns None.
+ """
+ retcode, out, err = read_pipe_full(c, *k, **kw)
if retcode != 0:
return None
else:
return decode_text_stream(out).rstrip()
+
def p4_read_pipe(c, ignore_error=False, raw=False, *k, **kw):
real_cmd = p4_build_cmd(c)
return read_pipe(real_cmd, ignore_error, raw=raw, *k, **kw)
+
def read_pipe_lines(c, raw=False, *k, **kw):
if verbose:
sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c)))
@@ -303,31 +338,36 @@ def read_pipe_lines(c, raw=False, *k, **kw):
die('Command failed: {}'.format(' '.join(c)))
return lines
+
def p4_read_pipe_lines(c, *k, **kw):
- """Specifically invoke p4 on the command supplied. """
+ """Specifically invoke p4 on the command supplied."""
real_cmd = p4_build_cmd(c)
return read_pipe_lines(real_cmd, *k, **kw)
+
def p4_has_command(cmd):
- """Ask p4 for help on this command. If it returns an error, the
- command does not exist in this version of p4."""
+ """Ask p4 for help on this command. If it returns an error, the command
+ does not exist in this version of p4.
+ """
real_cmd = p4_build_cmd(["help", cmd])
p = subprocess.Popen(real_cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
p.communicate()
return p.returncode == 0
+
def p4_has_move_command():
- """See if the move command exists, that it supports -k, and that
- it has not been administratively disabled. The arguments
- must be correct, but the filenames do not have to exist. Use
- ones with wildcards so even if they exist, it will fail."""
+ """See if the move command exists, that it supports -k, and that it has not
+ been administratively disabled. The arguments must be correct, but the
+ filenames do not have to exist. Use ones with wildcards so even if they
+ exist, it will fail.
+ """
if not p4_has_command("move"):
return False
cmd = p4_build_cmd(["move", "-k", "@from", "@to"])
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- (out, err) = p.communicate()
+ out, err = p.communicate()
err = decode_text_stream(err)
# return code will be 1 in either case
if err.find("Invalid option") >= 0:
@@ -337,6 +377,7 @@ def p4_has_move_command():
# assume it failed because @... was invalid changelist
return True
+
def system(cmd, ignore_error=False, *k, **kw):
if verbose:
sys.stderr.write("executing {}\n".format(
@@ -347,19 +388,22 @@ def system(cmd, ignore_error=False, *k, **kw):
return retcode
+
def p4_system(cmd, *k, **kw):
- """Specifically invoke p4 as the system command. """
+ """Specifically invoke p4 as the system command."""
real_cmd = p4_build_cmd(cmd)
retcode = subprocess.call(real_cmd, *k, **kw)
if retcode:
raise subprocess.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
- """
+ """Check if we can access Perforce - account still logged in."""
+
results = p4CmdList(["login", "-s"])
if len(results) == 0:
@@ -402,62 +446,78 @@ def p4_check_access(min_expiration=1):
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
- hopefully is the interesting version bit.
+ """Read the version string, showing just the last line, which hopefully is
+ the interesting version bit.
$ p4 -V
Perforce - The Fast Software Configuration Management System.
Copyright 1995-2011 Perforce Software. All rights reserved.
Rev. P4/NTX86/2011.1/393975 (2011/12/16).
- """
+ """
global _p4_version_string
if not _p4_version_string:
a = p4_read_pipe_lines(["-V"])
_p4_version_string = a[-1].rstrip()
return _p4_version_string
+
def p4_integrate(src, dest):
p4_system(["integrate", "-Dt", wildcard_encode(src), wildcard_encode(dest)])
+
def p4_sync(f, *options):
p4_system(["sync"] + list(options) + [wildcard_encode(f)])
+
def p4_add(f):
- # forcibly add file names with wildcards
+ """Forcibly add file names with wildcards."""
if wildcard_present(f):
p4_system(["add", "-f", f])
else:
p4_system(["add", f])
+
def p4_delete(f):
p4_system(["delete", wildcard_encode(f)])
+
def p4_edit(f, *options):
p4_system(["edit"] + list(options) + [wildcard_encode(f)])
+
def p4_revert(f):
p4_system(["revert", wildcard_encode(f)])
+
def p4_reopen(type, f):
p4_system(["reopen", "-t", type, wildcard_encode(f)])
+
def p4_reopen_in_change(changelist, files):
cmd = ["reopen", "-c", str(changelist)] + files
p4_system(cmd)
+
def p4_move(src, dest):
p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)])
+
def p4_last_change():
results = p4CmdList(["changes", "-m", "1"], skip_info=True)
return int(results[0]['change'])
+
def p4_describe(change, shelved=False):
- """Make sure it returns a valid result by checking for
- the presence of field "time". Return a dict of the
- results."""
+ """Make sure it returns a valid result by checking for the presence of
+ field "time".
+
+ Return a dict of the results.
+ """
cmd = ["describe", "-s"]
if shelved:
@@ -482,12 +542,11 @@ def p4_describe(change, shelved=False):
return d
-#
-# Canonicalize the p4 type and return a tuple of the
-# base type, plus any modifiers. See "p4 help filetypes"
-# for a list and explanation.
-#
+
def split_p4_type(p4type):
+ """Canonicalize the p4 type and return a tuple of the base type, plus any
+ modifiers. See "p4 help filetypes" for a list and explanation.
+ """
p4_filetypes_historical = {
"ctempobj": "binary+Sw",
@@ -517,18 +576,19 @@ def split_p4_type(p4type):
mods = s[1]
return (base, mods)
-#
-# return the raw p4 type of a file (text, text+ko, etc)
-#
+
def p4_type(f):
+ """Return the raw p4 type of a file (text, text+ko, etc)."""
+
results = p4CmdList(["fstat", "-T", "headType", wildcard_encode(f)])
return results[0]['headType']
-#
-# Given a type base and modifier, return a regexp matching
-# the keywords that can be expanded in the file
-#
+
def p4_keywords_regexp_for_type(base, type_mods):
+ """Given a type base and modifier, return a regexp matching the keywords
+ that can be expanded in the file.
+ """
+
if base in ("text", "unicode", "binary"):
if "ko" in type_mods:
return re_ko_keywords
@@ -539,21 +599,23 @@ def p4_keywords_regexp_for_type(base, type_mods):
else:
return None
-#
-# Given a file, return a regexp matching the possible
-# RCS keywords that will be expanded, or None for files
-# with kw expansion turned off.
-#
+
def p4_keywords_regexp_for_file(file):
+ """Given a file, return a regexp matching the possible RCS keywords that
+ will be expanded, or None for files with kw expansion turned off.
+ """
+
if not os.path.exists(file):
return None
else:
- (type_base, type_mods) = split_p4_type(p4_type(file))
+ type_base, type_mods = split_p4_type(p4_type(file))
return p4_keywords_regexp_for_type(type_base, type_mods)
+
def setP4ExecBit(file, mode):
- # Reopens an already open file and changes the execute bit to match
- # the execute bit setting in the passed in mode.
+ """Reopens an already open file and changes the execute bit to match the
+ execute bit setting in the passed in mode.
+ """
p4Type = "+x"
@@ -566,8 +628,9 @@ def setP4ExecBit(file, mode):
p4_reopen(p4Type, file)
+
def getP4OpenedType(file):
- # Returns the perforce file type for the given file.
+ """Returns the perforce file type for the given file."""
result = p4_read_pipe(["opened", wildcard_encode(file)])
match = re.match(".*\((.+)\)( \*exclusive\*)?\r?$", result)
@@ -576,8 +639,10 @@ def getP4OpenedType(file):
else:
die("Could not determine file type for %s (result: '%s')" % (file, result))
-# Return the set of all p4 labels
+
def getP4Labels(depotPaths):
+ """Return the set of all p4 labels."""
+
labels = set()
if not isinstance(depotPaths, list):
depotPaths = [depotPaths]
@@ -588,34 +653,39 @@ def getP4Labels(depotPaths):
return labels
-# Return the set of all git tags
+
def getGitTags():
+ """Return the set of all git tags."""
+
gitTags = set()
for line in read_pipe_lines(["git", "tag"]):
tag = line.strip()
gitTags.add(tag)
return gitTags
+
_diff_tree_pattern = None
+
def parseDiffTreeEntry(entry):
"""Parses a single diff tree entry into its component elements.
- See git-diff-tree(1) manpage for details about the format of the diff
- output. This method returns a dictionary with the following elements:
+ See git-diff-tree(1) manpage for details about the format of the diff
+ output. This method returns a dictionary with the following elements:
- src_mode - The mode of the source file
- dst_mode - The mode of the destination file
- src_sha1 - The sha1 for the source file
- dst_sha1 - The sha1 fr the destination file
- status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
- status_score - The score for the status (applicable for 'C' and 'R'
- statuses). This is None if there is no score.
- src - The path for the source file.
- dst - The path for the destination file. This is only present for
- copy or renames. If it is not present, this is None.
+ src_mode - The mode of the source file
+ dst_mode - The mode of the destination file
+ src_sha1 - The sha1 for the source file
+ dst_sha1 - The sha1 fr the destination file
+ status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
+ status_score - The score for the status (applicable for 'C' and 'R'
+ statuses). This is None if there is no score.
+ src - The path for the source file.
+ dst - The path for the destination file. This is only present for
+ copy or renames. If it is not present, this is None.
- If the pattern is not matched, None is returned."""
+ If the pattern is not matched, None is returned.
+ """
global _diff_tree_pattern
if not _diff_tree_pattern:
@@ -635,41 +705,55 @@ def parseDiffTreeEntry(entry):
}
return None
+
def isModeExec(mode):
- # Returns True if the given git mode represents an executable file,
- # otherwise False.
+ """Returns True if the given git mode represents an executable file,
+ otherwise False.
+ """
return mode[-3:] == "755"
+
class P4Exception(Exception):
- """ Base class for exceptions from the p4 client """
+ """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 """
+ """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 """
+ """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
+
class P4CommandException(P4Exception):
- """ Something went wrong calling p4 which means we have to give up """
+ """Something went wrong calling p4 which means we have to give up."""
+
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
+
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,
errors_as_exceptions=False, *k, **kw):
@@ -746,12 +830,14 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
return result
+
def p4Cmd(cmd, *k, **kw):
list = p4CmdList(cmd, *k, **kw)
result = {}
for entry in list:
result.update(entry)
- return result;
+ return result
+
def p4Where(depotPath):
if not depotPath.endswith("/"):
@@ -773,7 +859,7 @@ def p4Where(depotPath):
if data[:space] == depotPath:
output = entry
break
- if output == None:
+ if output is None:
return ""
if output["code"] == "error":
return ""
@@ -789,48 +875,54 @@ def p4Where(depotPath):
clientPath = clientPath[:-3]
return clientPath
+
def currentGitBranch():
return read_pipe_text(["git", "symbolic-ref", "--short", "-q", "HEAD"])
+
def isValidGitDir(path):
- return git_dir(path) != None
+ return git_dir(path) is not None
+
def parseRevision(ref):
return read_pipe(["git", "rev-parse", ref]).strip()
+
def branchExists(ref):
rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref],
ignore_error=True)
return len(rev) > 0
+
def extractLogMessageFromGitCommit(commit):
logMessage = ""
- ## fixme: title is first line of commit, not 1st paragraph.
+ # fixme: title is first line of commit, not 1st paragraph.
foundTitle = False
for log in read_pipe_lines(["git", "cat-file", "commit", commit]):
- if not foundTitle:
- if len(log) == 1:
- foundTitle = True
- continue
+ if not foundTitle:
+ if len(log) == 1:
+ foundTitle = True
+ continue
- logMessage += log
+ logMessage += log
return logMessage
+
def extractSettingsGitLog(log):
values = {}
for line in log.split("\n"):
line = line.strip()
- m = re.search (r"^ *\[git-p4: (.*)\]$", line)
+ m = re.search(r"^ *\[git-p4: (.*)\]$", line)
if not m:
continue
- assignments = m.group(1).split (':')
+ assignments = m.group(1).split(':')
for a in assignments:
- vals = a.split ('=')
+ vals = a.split('=')
key = vals[0].strip()
- val = ('='.join (vals[1:])).strip()
- if val.endswith ('\"') and val.startswith('"'):
+ val = ('='.join(vals[1:])).strip()
+ if val.endswith('\"') and val.startswith('"'):
val = val[1:-1]
values[key] = val
@@ -842,41 +934,49 @@ def extractSettingsGitLog(log):
values['depot-paths'] = paths.split(',')
return values
+
def gitBranchExists(branch):
proc = subprocess.Popen(["git", "rev-parse", branch],
- stderr=subprocess.PIPE, stdout=subprocess.PIPE);
- return proc.wait() == 0;
+ stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+ return proc.wait() == 0
+
def gitUpdateRef(ref, newvalue):
subprocess.check_call(["git", "update-ref", ref, newvalue])
+
def gitDeleteRef(ref):
subprocess.check_call(["git", "update-ref", "-d", ref])
+
_gitConfig = {}
+
def gitConfig(key, typeSpecifier=None):
if key not in _gitConfig:
- cmd = [ "git", "config" ]
+ cmd = ["git", "config"]
if typeSpecifier:
- cmd += [ typeSpecifier ]
- cmd += [ key ]
+ cmd += [typeSpecifier]
+ cmd += [key]
s = read_pipe(cmd, ignore_error=True)
_gitConfig[key] = s.strip()
return _gitConfig[key]
+
def gitConfigBool(key):
"""Return a bool, using git config --bool. It is True only if the
variable is set to true, and False if set to false or not present
- in the config."""
+ in the config.
+ """
if key not in _gitConfig:
_gitConfig[key] = gitConfig(key, '--bool') == "true"
return _gitConfig[key]
+
def gitConfigInt(key):
if key not in _gitConfig:
- cmd = [ "git", "config", "--int", key ]
+ cmd = ["git", "config", "--int", key]
s = read_pipe(cmd, ignore_error=True)
v = s.strip()
try:
@@ -885,6 +985,7 @@ def gitConfigInt(key):
_gitConfig[key] = None
return _gitConfig[key]
+
def gitConfigList(key):
if key not in _gitConfig:
s = read_pipe(["git", "config", "--get-all", key], ignore_error=True)
@@ -893,12 +994,43 @@ def gitConfigList(key):
_gitConfig[key] = []
return _gitConfig[key]
+def fullP4Ref(incomingRef, importIntoRemotes=True):
+ """Standardize a given provided p4 ref value to a full git ref:
+ refs/foo/bar/branch -> use it exactly
+ p4/branch -> prepend refs/remotes/ or refs/heads/
+ branch -> prepend refs/remotes/p4/ or refs/heads/p4/"""
+ if incomingRef.startswith("refs/"):
+ return incomingRef
+ if importIntoRemotes:
+ prepend = "refs/remotes/"
+ else:
+ prepend = "refs/heads/"
+ if not incomingRef.startswith("p4/"):
+ prepend += "p4/"
+ return prepend + incomingRef
+
+def shortP4Ref(incomingRef, importIntoRemotes=True):
+ """Standardize to a "short ref" if possible:
+ refs/foo/bar/branch -> ignore
+ refs/remotes/p4/branch or refs/heads/p4/branch -> shorten
+ p4/branch -> shorten"""
+ if importIntoRemotes:
+ longprefix = "refs/remotes/p4/"
+ else:
+ longprefix = "refs/heads/p4/"
+ if incomingRef.startswith(longprefix):
+ return incomingRef[len(longprefix):]
+ if incomingRef.startswith("p4/"):
+ return incomingRef[3:]
+ return incomingRef
+
def p4BranchesInGit(branchesAreInRemotes=True):
"""Find all the branches whose names start with "p4/", looking
in remotes or heads as specified by the argument. Return
a dictionary of { branch: revision } for each one found.
The branch names are the short names, without any
- "p4/" prefix."""
+ "p4/" prefix.
+ """
branches = {}
@@ -925,10 +1057,11 @@ def p4BranchesInGit(branchesAreInRemotes=True):
return branches
+
def branch_exists(branch):
"""Make sure that the given ref name really exists."""
- cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ]
+ cmd = ["git", "rev-parse", "--symbolic", "--verify", branch]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, _ = p.communicate()
out = decode_text_stream(out)
@@ -937,7 +1070,8 @@ def branch_exists(branch):
# expect exactly one line of output: the branch name
return out.rstrip() == branch
-def findUpstreamBranchPoint(head = "HEAD"):
+
+def findUpstreamBranchPoint(head="HEAD"):
branches = p4BranchesInGit()
# map from depot-path to branch name
branchByDepotPath = {}
@@ -946,8 +1080,12 @@ def findUpstreamBranchPoint(head = "HEAD"):
log = extractLogMessageFromGitCommit(tip)
settings = extractSettingsGitLog(log)
if "depot-paths" in settings:
+ git_branch = "remotes/p4/" + branch
paths = ",".join(settings["depot-paths"])
- branchByDepotPath[paths] = "remotes/p4/" + branch
+ branchByDepotPath[paths] = git_branch
+ if "change" in settings:
+ paths = paths + ";" + settings["change"]
+ branchByDepotPath[paths] = git_branch
settings = None
parent = 0
@@ -957,6 +1095,10 @@ def findUpstreamBranchPoint(head = "HEAD"):
settings = extractSettingsGitLog(log)
if "depot-paths" in settings:
paths = ",".join(settings["depot-paths"])
+ if "change" in settings:
+ expaths = paths + ";" + settings["change"]
+ if expaths in branchByDepotPath:
+ return [branchByDepotPath[expaths], settings]
if paths in branchByDepotPath:
return [branchByDepotPath[paths], settings]
@@ -964,7 +1106,8 @@ def findUpstreamBranchPoint(head = "HEAD"):
return ["", settings]
-def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
+
+def createOrUpdateBranchesFromOrigin(localRefPrefix="refs/remotes/p4/", silent=True):
if not silent:
print("Creating/updating branch(es) in %s based on origin branch(es)"
% localRefPrefix)
@@ -981,8 +1124,7 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent
originHead = line
original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
- if ('depot-paths' not in original
- or 'change' not in original):
+ if 'depot-paths' not in original or 'change' not in original:
continue
update = False
@@ -1011,8 +1153,9 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent
if update:
system(["git", "update-ref", remoteHead, originHead])
+
def originP4BranchesExist():
- return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
+ return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
def p4ParseNumericChangeRange(parts):
@@ -1024,12 +1167,14 @@ def p4ParseNumericChangeRange(parts):
return (changeStart, changeEnd)
+
def chooseBlockSize(blockSize):
if blockSize:
return blockSize
else:
return defaultBlockSize
+
def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
assert depotPaths
@@ -1047,7 +1192,7 @@ def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
parts = changeRange.split(',')
assert len(parts) == 2
try:
- (changeStart, changeEnd) = p4ParseNumericChangeRange(parts)
+ changeStart, changeEnd = p4ParseNumericChangeRange(parts)
block_size = chooseBlockSize(requestedBlockSize)
except ValueError:
changeStart = parts[0][1:]
@@ -1085,7 +1230,8 @@ def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
else:
block_size = max(2, block_size // 2)
- if verbose: print("block size error, retrying with block size {0}".format(block_size))
+ 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))
@@ -1107,21 +1253,25 @@ def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
changes = sorted(changes)
return changes
+
def p4PathStartsWith(path, prefix):
- # This method tries to remedy a potential mixed-case issue:
- #
- # If UserA adds //depot/DirA/file1
- # and UserB adds //depot/dira/file2
- #
- # we may or may not have a problem. If you have core.ignorecase=true,
- # we treat DirA and dira as the same directory
+ """This method tries to remedy a potential mixed-case issue:
+
+ If UserA adds //depot/DirA/file1
+ and UserB adds //depot/dira/file2
+
+ we may or may not have a problem. If you have core.ignorecase=true,
+ we treat DirA and dira as the same directory.
+ """
if gitConfigBool("core.ignorecase"):
return path.lower().startswith(prefix.lower())
return path.startswith(prefix)
+
def getClientSpec():
"""Look at the p4 client spec, create a View() object that contains
- all the mappings, and return it."""
+ all the mappings, and return it.
+ """
specList = p4CmdList(["client", "-o"])
if len(specList) != 1:
@@ -1135,7 +1285,7 @@ def getClientSpec():
client_name = entry["Client"]
# just the keys that start with "View"
- view_keys = [ k for k in entry.keys() if k.startswith("View") ]
+ view_keys = [k for k in entry.keys() if k.startswith("View")]
# hold this new View
view = View(client_name)
@@ -1149,6 +1299,7 @@ def getClientSpec():
return view
+
def getClientRoot():
"""Grab the client directory."""
@@ -1162,12 +1313,15 @@ def getClientRoot():
return entry["Root"]
-#
-# P4 wildcards are not allowed in filenames. P4 complains
-# if you simply add them, but you can force it with "-f", in
-# which case it translates them into %xx encoding internally.
-#
+
def wildcard_decode(path):
+ """Decode P4 wildcards into %xx encoding
+
+ P4 wildcards are not allowed in filenames. P4 complains if you simply
+ add them, but you can force it with "-f", in which case it translates
+ them into %xx encoding internally.
+ """
+
# Search for and fix just these four characters. Do % last so
# that fixing it does not inadvertently create new %-escapes.
# Cannot have * in a filename in windows; untested as to
@@ -1179,7 +1333,10 @@ def wildcard_decode(path):
.replace("%25", "%")
return path
+
def wildcard_encode(path):
+ """Encode %xx coded wildcards into P4 coding."""
+
# do % first to avoid double-encoding the %s introduced here
path = path.replace("%", "%25") \
.replace("*", "%2A") \
@@ -1187,10 +1344,12 @@ def wildcard_encode(path):
.replace("@", "%40")
return path
+
def wildcard_present(path):
m = re.search("[*#@%]", path)
return m is not None
+
class LargeFileSystem(object):
"""Base class for large file system support."""
@@ -1199,13 +1358,15 @@ def __init__(self, writeToGitStream):
self.writeToGitStream = writeToGitStream
def generatePointer(self, cloneDestination, contentFile):
- """Return the content of a pointer file that is stored in Git instead of
- the actual content."""
+ """Return the content of a pointer file that is stored in Git instead
+ of the actual content.
+ """
assert False, "Method 'generatePointer' required in " + self.__class__.__name__
def pushFile(self, localLargeFile):
"""Push the actual content which is not stored in the Git repository to
- a server."""
+ a server.
+ """
assert False, "Method 'pushFile' required in " + self.__class__.__name__
def hasLargeFileExtension(self, relPath):
@@ -1253,10 +1414,11 @@ def isLargeFile(self, relPath):
def processContent(self, git_mode, relPath, contents):
"""Processes the content of git fast import. This method decides if a
file is stored in the large file system and handles all necessary
- steps."""
+ steps.
+ """
if self.exceedsLargeFileThreshold(relPath, contents) or self.hasLargeFileExtension(relPath):
contentTempFile = self.generateTempFile(contents)
- (pointer_git_mode, contents, localLargeFile) = self.generatePointer(contentTempFile)
+ pointer_git_mode, contents, localLargeFile = self.generatePointer(contentTempFile)
if pointer_git_mode:
git_mode = pointer_git_mode
if localLargeFile:
@@ -1272,12 +1434,14 @@ def processContent(self, git_mode, relPath, contents):
sys.stderr.write("%s moved to large file system (%s)\n" % (relPath, localLargeFile))
return (git_mode, contents)
+
class MockLFS(LargeFileSystem):
"""Mock large file system for testing."""
def generatePointer(self, contentFile):
"""The pointer content is the original content prefixed with "pointer-".
- The local filename of the large file storage is derived from the file content.
+ The local filename of the large file storage is derived from the
+ file content.
"""
with open(contentFile, 'r') as f:
content = next(f)
@@ -1287,17 +1451,19 @@ def generatePointer(self, contentFile):
return (gitMode, pointerContents, localLargeFile)
def pushFile(self, localLargeFile):
- """The remote filename of the large file storage is the same as the local
- one but in a different directory.
+ """The remote filename of the large file storage is the same as the
+ local one but in a different directory.
"""
remotePath = os.path.join(os.path.dirname(localLargeFile), '..', 'remote')
if not os.path.exists(remotePath):
os.makedirs(remotePath)
shutil.copyfile(localLargeFile, os.path.join(remotePath, os.path.basename(localLargeFile)))
+
class GitLFS(LargeFileSystem):
"""Git LFS as backend for the git-p4 large file system.
- See https://git-lfs.github.com/ for details."""
+ See https://git-lfs.github.com/ for details.
+ """
def __init__(self, *args):
LargeFileSystem.__init__(self, *args)
@@ -1383,9 +1549,10 @@ def processContent(self, git_mode, relPath, contents):
else:
return LargeFileSystem.processContent(self, git_mode, relPath, contents)
+
class Command:
- delete_actions = ( "delete", "move/delete", "purge" )
- add_actions = ( "add", "branch", "move/add" )
+ delete_actions = ("delete", "move/delete", "purge")
+ add_actions = ("add", "branch", "move/add")
def __init__(self):
self.usage = "usage: %prog [options]"
@@ -1398,6 +1565,7 @@ def ensure_value(self, attr, value):
setattr(self, attr, value)
return getattr(self, attr)
+
class P4UserMap:
def __init__(self):
self.userMapFromPerforceServer = False
@@ -1415,7 +1583,7 @@ def p4UserId(self):
die("Could not find your p4 user id")
def p4UserIsMe(self, p4User):
- # return True if the given p4 user is actually me
+ """Return True if the given p4 user is actually me."""
me = self.p4UserId()
if not p4User or p4User != me:
return False
@@ -1468,6 +1636,7 @@ def loadUserMapFromCache(self):
except IOError:
self.getUserMapFromPerforceServer()
+
class P4Submit(Command, P4UserMap):
conflict_behavior_choices = ("ask", "skip", "quit")
@@ -1560,20 +1729,20 @@ def check(self):
die("You have files opened with perforce! Close them before starting the sync.")
def separate_jobs_from_description(self, message):
- """Extract and return a possible Jobs field in the commit
- message. It goes into a separate section in the p4 change
- specification.
+ """Extract and return a possible Jobs field in the commit message. It
+ goes into a separate section in the p4 change specification.
- A jobs line starts with "Jobs:" and looks like a new field
- in a form. Values are white-space separated on the same
- line or on following lines that start with a tab.
+ A jobs line starts with "Jobs:" and looks like a new field in a
+ form. Values are white-space separated on the same line or on
+ following lines that start with a tab.
- This does not parse and extract the full git commit message
- like a p4 form. It just sees the Jobs: line as a marker
- to pass everything from then on directly into the p4 form,
- but outside the description section.
+ This does not parse and extract the full git commit message like a
+ p4 form. It just sees the Jobs: line as a marker to pass everything
+ from then on directly into the p4 form, but outside the description
+ section.
- Return a tuple (stripped log message, jobs string)."""
+ Return a tuple (stripped log message, jobs string).
+ """
m = re.search(r'^Jobs:', message, re.MULTILINE)
if m is None:
@@ -1584,9 +1753,10 @@ def separate_jobs_from_description(self, message):
return (stripped_message, jobtext)
def prepareLogMessage(self, template, message, jobs):
- """Edits the template returned from "p4 change -o" to insert
- the message in the Description field, and the jobs text in
- the Jobs field."""
+ """Edits the template returned from "p4 change -o" to insert the
+ message in the Description field, and the jobs text in the Jobs
+ field.
+ """
result = ""
inDescriptionSection = False
@@ -1616,8 +1786,10 @@ def prepareLogMessage(self, template, message, jobs):
return result
def patchRCSKeywords(self, file, regexp):
- # Attempt to zap the RCS keywords in a p4 controlled file matching the given regex
- (handle, outFileName) = tempfile.mkstemp(dir='.')
+ """Attempt to zap the RCS keywords in a p4 controlled file matching the
+ given regex.
+ """
+ handle, outFileName = tempfile.mkstemp(dir='.')
try:
with os.fdopen(handle, "wb") as outFile, open(file, "rb") as inFile:
for line in inFile.readlines():
@@ -1633,21 +1805,23 @@ def patchRCSKeywords(self, file, regexp):
print("Patched up RCS keywords in %s" % file)
- def p4UserForCommit(self,id):
- # Return the tuple (perforce user,git email) for a given git commit id
+ 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", id])
gitEmail = gitEmail.strip()
if gitEmail not in self.emails:
- return (None,gitEmail)
+ return (None, gitEmail)
else:
- return (self.emails[gitEmail],gitEmail)
+ return (self.emails[gitEmail], gitEmail)
- def checkValidP4Users(self,commits):
- # check if any git authors cannot be mapped to p4 users
+ def checkValidP4Users(self, commits):
+ """Check if any git authors cannot be mapped to p4 users."""
for id in commits:
- (user,email) = self.p4UserForCommit(id)
+ user, email = self.p4UserForCommit(id)
if not user:
msg = "Cannot find p4 user for email %s in commit %s." % (email, id)
if gitConfigBool("git-p4.allowMissingP4Users"):
@@ -1656,10 +1830,12 @@ def checkValidP4Users(self,commits):
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.
+ """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:
@@ -1675,14 +1851,16 @@ def lastP4Changelist(self):
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.
+ """Fixup the user field of a changelist after it has been submitted."""
changes = p4CmdList(["change", "-o", changelist])
if len(changes) != 1:
die("Bad output from p4 change modifying %s to user %s" %
(changelist, newUser))
c = changes[0]
- if c['User'] == newUser: return # nothing to do
+ if c['User'] == newUser:
+ # Nothing to do
+ return
c['User'] = newUser
# p4 does not understand format version 3 and above
input = marshal.dumps(c, 2)
@@ -1698,8 +1876,9 @@ def modifyChangelistUser(self, changelist, newUser):
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.
+ """Check to see if we have p4 admin or super-user permissions, either
+ of which are required to modify changelists.
+ """
results = p4CmdList(["protects", self.depotPath])
for r in results:
if 'perm' in r:
@@ -1711,13 +1890,15 @@ def canChangeChangelists(self):
def prepareSubmitTemplate(self, changelist=None):
"""Run "p4 change -o" to grab a change specification template.
+
This does not use "p4 -G", as it is nice to keep the submission
template in original order, since a human might edit it.
Remove lines in the Files section that show changes to files
- outside the depot path we're committing into."""
+ outside the depot path we're committing into.
+ """
- [upstream, settings] = findUpstreamBranchPoint()
+ upstream, settings = findUpstreamBranchPoint()
template = """\
# A Perforce Change Specification.
@@ -1778,8 +1959,10 @@ def prepareSubmitTemplate(self, changelist=None):
return template
def edit_template(self, template_file):
- """Invoke the editor to let the user change the submission
- message. Return true if okay to continue with the submit."""
+ """Invoke the editor to let the user change the submission message.
+
+ Return true if okay to continue with the submit.
+ """
# if configured to skip the editing part, just submit
if gitConfigBool("git-p4.skipSubmitEdit"):
@@ -1838,7 +2021,9 @@ def get_diff_description(self, editedFiles, filesToAdd, symlinks):
for line in f.readlines():
newdiff += "+" + line
except UnicodeDecodeError:
- pass # Found non-text data and skip, since diff description should only include text
+ # Found non-text data and skip, since diff description
+ # should only include text
+ pass
f.close()
return (diff + newdiff).replace('\r\n', '\n')
@@ -1849,7 +2034,7 @@ def applyCommit(self, id):
print("Applying", read_pipe(["git", "show", "-s",
"--format=format:%h %s", id]))
- (p4User, gitEmail) = self.p4UserForCommit(id)
+ p4User, gitEmail = self.p4UserForCommit(id)
diff = read_pipe_lines(
["git", "diff-tree", "-r"] + self.diffOpts + ["{}^".format(id), id])
@@ -1956,8 +2141,8 @@ def applyCommit(self, id):
if regexp:
# this file is a possibility...look for RCS keywords.
for line in read_pipe_lines(
- ["git", "diff", "%s^..%s" % (id, id), file],
- raw=True):
+ ["git", "diff", "%s^..%s" % (id, id), file],
+ raw=True):
if regexp.search(line):
if verbose:
print("got keyword match on %s in %s in %s" % (regex.pattern, line, file))
@@ -2014,13 +2199,13 @@ def applyCommit(self, id):
#
logMessage = extractLogMessageFromGitCommit(id)
logMessage = logMessage.strip()
- (logMessage, jobs) = self.separate_jobs_from_description(logMessage)
+ logMessage, jobs = self.separate_jobs_from_description(logMessage)
template = self.prepareSubmitTemplate(update_shelve)
submitTemplate = self.prepareLogMessage(template, logMessage, jobs)
if self.preserveUser:
- submitTemplate += "\n######## Actual user %s, modified after commit\n" % p4User
+ submitTemplate += "\n######## Actual user %s, modified after commit\n" % p4User
if self.checkAuthorship and not self.p4UserIsMe(p4User):
submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
@@ -2032,7 +2217,7 @@ def applyCommit(self, id):
submitTemplate += separatorLine
submitTemplate += self.get_diff_description(editedFiles, filesToAdd, symlinks)
- (handle, fileName) = tempfile.mkstemp()
+ handle, fileName = tempfile.mkstemp()
tmpFile = os.fdopen(handle, "w+b")
if self.isWindows:
submitTemplate = submitTemplate.replace("\n", "\r\n")
@@ -2059,13 +2244,13 @@ def applyCommit(self, id):
print(" " + self.clientPath)
print("")
print("To submit, use \"p4 submit\" to write a new description,")
- print("or \"p4 submit -i <%s\" to use the one prepared by" \
+ print("or \"p4 submit -i <%s\" to use the one prepared by"
" \"git p4\"." % fileName)
print("You can delete the file \"%s\" when finished." % fileName)
if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
- print("To preserve change ownership by user %s, you must\n" \
- "do \"p4 change -f <change>\" after submitting and\n" \
+ print("To preserve change ownership by user %s, you must\n"
+ "do \"p4 change -f <change>\" after submitting and\n"
"edit the User field.")
if pureRenameCopy:
print("After submitting, renamed files must be re-synced.")
@@ -2133,9 +2318,9 @@ def applyCommit(self, id):
# Revert changes if we skip this patch
if not submitted or self.shelve:
if self.shelve:
- print ("Reverting shelved files.")
+ print("Reverting shelved files.")
else:
- print ("Submission cancelled, undoing p4 changes.")
+ print("Submission cancelled, undoing p4 changes.")
sys.stdout.flush()
for f in editedFiles | filesToDelete:
p4_revert(f)
@@ -2147,9 +2332,11 @@ def applyCommit(self, id):
os.remove(fileName)
return submitted
- # Export git tags as p4 labels. Create a p4 label and then tag
- # with that.
def exportGitTags(self, gitTags):
+ """Export git tags as p4 labels. Create a p4 label and then tag with
+ that.
+ """
+
validLabelRegexp = gitConfig("git-p4.labelExportRegexp")
if len(validLabelRegexp) == 0:
validLabelRegexp = defaultLabelRegexp
@@ -2195,7 +2382,7 @@ def exportGitTags(self, gitTags):
# Create the label - use the same view as the client spec we are using
clientSpec = getClientSpec()
- labelTemplate = "Label: %s\n" % name
+ labelTemplate = "Label: %s\n" % name
labelTemplate += "Description:\n"
for b in body:
labelTemplate += "\t" + b + "\n"
@@ -2206,7 +2393,7 @@ def exportGitTags(self, gitTags):
if self.dry_run:
print("Would create p4 label %s for tag" % name)
elif self.prepare_p4_only:
- print("Not creating p4 label %s for tag due to option" \
+ print("Not creating p4 label %s for tag due to option"
" --prepare-p4-only" % name)
else:
p4_write_pipe(["label", "-i"], labelTemplate)
@@ -2237,7 +2424,7 @@ def run(self, args):
if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
die("%s is not in git-p4.allowSubmit" % self.master)
- [upstream, settings] = findUpstreamBranchPoint()
+ upstream, settings = findUpstreamBranchPoint()
self.depotPath = settings['depot-paths'][0]
if len(self.origin) == 0:
self.origin = upstream
@@ -2371,13 +2558,13 @@ def run(self, args):
if not self.no_verify:
try:
if not run_git_hook("p4-pre-submit"):
- print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nYou can skip " \
- "this pre-submission check by adding\nthe command line option '--no-verify', " \
+ print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nYou can skip "
+ "this pre-submission check by adding\nthe command line option '--no-verify', "
"however,\nthis will also skip the p4-changelist hook as well.")
sys.exit(1)
except Exception as e:
- print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nThe hook failed "\
- "with the error '{0}'".format(e.message) )
+ print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nThe hook failed "
+ "with the error '{0}'".format(e.message))
sys.exit(1)
#
@@ -2399,7 +2586,7 @@ def run(self, args):
applied.append(commit)
if self.prepare_p4_only:
if i < last:
- print("Processing only the first commit due to option" \
+ print("Processing only the first commit due to option"
" --prepare-p4-only")
break
else:
@@ -2469,13 +2656,15 @@ def run(self, args):
# exit with error unless everything applied perfectly
if len(commits) != len(applied):
- sys.exit(1)
+ sys.exit(1)
return True
+
class View(object):
- """Represent a p4 view ("p4 help views"), and map files in a
- repo according to the view."""
+ """Represent a p4 view ("p4 help views"), and map files in a repo according
+ to the view.
+ """
def __init__(self, client_name):
self.mappings = []
@@ -2484,9 +2673,10 @@ def __init__(self, client_name):
self.client_spec_path_cache = {}
def append(self, view_line):
- """Parse a view line, splitting it into depot and client
- sides. Append to self.mappings, preserving order. This
- is only needed for tag creation."""
+ """Parse a view line, splitting it into depot and client sides. Append
+ to self.mappings, preserving order. This is only needed for tag
+ creation.
+ """
# Split the view line into exactly two words. P4 enforces
# structure on these lines that simplifies this quite a bit.
@@ -2535,7 +2725,7 @@ def convert_client_path(self, clientFile):
return clientFile[len(self.client_prefix):]
def update_client_spec_path_cache(self, files):
- """ Caching file paths by "p4 where" batch query """
+ """Caching file paths by "p4 where" batch query."""
# List depot file paths exclude that already cached
fileArgs = [f['path'] for f in files if decode_path(f['path']) not in self.client_spec_path_cache]
@@ -2567,9 +2757,11 @@ def update_client_spec_path_cache(self, files):
self.client_spec_path_cache[depotFile] = b''
def map_in_client(self, depot_path):
- """Return the relative location in the client where this
- depot file should live. Returns "" if the file should
- not be mapped in the client."""
+ """Return the relative location in the client where this depot file
+ should live.
+
+ Returns "" if the file should not be mapped in the client.
+ """
if gitConfigBool("core.ignorecase"):
depot_path = depot_path.lower()
@@ -2577,14 +2769,16 @@ def map_in_client(self, depot_path):
if depot_path in self.client_spec_path_cache:
return self.client_spec_path_cache[depot_path]
- die( "Error: %s is not found in client spec path" % depot_path )
+ die("Error: %s is not found in client spec path" % depot_path)
return ""
+
def cloneExcludeCallback(option, opt_str, value, parser):
# prepend "/" because the first "/" was consumed as part of the option itself.
# ("-//depot/A/..." becomes "/depot/A/..." after option parsing)
parser.values.cloneExclude += ["/" + re.sub(r"\.\.\.$", "", value)]
+
class P4Sync(Command, P4UserMap):
def __init__(self):
@@ -2665,8 +2859,8 @@ def __init__(self):
self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
self.labels = {}
- # Force a checkpoint in fast-import and wait for it to finish
def checkpoint(self):
+ """Force a checkpoint in fast-import and wait for it to finish."""
self.gitStream.write("checkpoint\n\n")
self.gitStream.write("progress checkpoint\n\n")
self.gitStream.flush()
@@ -2687,11 +2881,11 @@ def isPathWanted(self, path):
return True
return False
- def extractFilesFromCommit(self, commit, shelved=False, shelved_cl = 0):
+ def extractFilesFromCommit(self, commit, shelved=False, shelved_cl=0):
files = []
fnum = 0
while "depotFile%s" % fnum in commit:
- path = commit["depotFile%s" % fnum]
+ path = commit["depotFile%s" % fnum]
found = self.isPathWanted(decode_path(path))
if not found:
fnum = fnum + 1
@@ -2718,10 +2912,10 @@ def extractJobsFromCommit(self, commit):
return jobs
def stripRepoPath(self, path, prefixes):
- """When streaming files, this is called to map a p4 depot path
- to where it should go in git. The prefixes are either
- self.depotPaths, or self.branchPrefixes in the case of
- branch detection."""
+ """When streaming files, this is called to map a p4 depot path to where
+ it should go in git. The prefixes are either self.depotPaths, or
+ self.branchPrefixes in the case of branch detection.
+ """
if self.useClientSpec:
# branch detection moves files up a level (the branch name)
@@ -2750,8 +2944,9 @@ def stripRepoPath(self, path, prefixes):
return path
def splitFilesIntoBranches(self, commit):
- """Look at each depotFile in the commit to figure out to what
- branch it belongs."""
+ """Look at each depotFile in the commit to figure out to what branch it
+ belongs.
+ """
if self.clientSpecDirs:
files = self.extractFilesFromCommit(commit)
@@ -2811,10 +3006,12 @@ def encodeWithUTF8(self, path):
print('Path with non-ASCII characters detected. Used %s to encode: %s ' % (encoding, path))
return path
- # output one file from the P4 stream
- # - helper for streamP4Files
-
def streamOneP4File(self, file, contents):
+ """Output one file from the P4 stream.
+
+ This is a helper for streamP4Files().
+ """
+
file_path = file['depotFile']
relPath = self.stripRepoPath(decode_path(file_path), self.branchPrefixes)
@@ -2822,12 +3019,13 @@ def streamOneP4File(self, file, contents):
if 'fileSize' in self.stream_file:
size = int(self.stream_file['fileSize'])
else:
- size = 0 # deleted files don't get a fileSize apparently
+ # Deleted files don't get a fileSize apparently
+ size = 0
sys.stdout.write('\r%s --> %s (%s)\n' % (
file_path, relPath, format_size_human_readable(size)))
sys.stdout.flush()
- (type_base, type_mods) = split_p4_type(file["type"])
+ type_base, type_mods = split_p4_type(file["type"])
git_mode = "100644"
if "x" in type_mods:
@@ -2870,7 +3068,7 @@ def streamOneP4File(self, file, contents):
else:
if p4_version_string().find('/NT') >= 0:
text = text.replace(b'\r\n', b'\n')
- contents = [ text ]
+ contents = [text]
if type_base == "apple":
# Apple filetype files will be streamed as a concatenation of
@@ -2885,6 +3083,16 @@ def streamOneP4File(self, file, contents):
print("\nIgnoring apple filetype file %s" % file['depotFile'])
return
+ if type_base == "utf8":
+ # The type utf8 explicitly means utf8 *with BOM*. These are
+ # streamed just like regular text files, however, without
+ # the BOM in the stream.
+ # Therefore, to accurately import these files into git, we
+ # need to explicitly re-add the BOM before writing.
+ # 'contents' is a set of bytes in this case, so create the
+ # BOM prefix as a b'' literal.
+ contents = [b'\xef\xbb\xbf' + contents[0]] + contents[1:]
+
# Note that we do not try to de-mangle keywords on utf16 files,
# even though in theory somebody may want that.
regexp = p4_keywords_regexp_for_type(type_base, type_mods)
@@ -2892,7 +3100,7 @@ def streamOneP4File(self, file, contents):
contents = [regexp.sub(br'$\1$', c) for c in contents]
if self.largeFileSystem:
- (git_mode, contents) = self.largeFileSystem.processContent(git_mode, relPath, contents)
+ git_mode, contents = self.largeFileSystem.processContent(git_mode, relPath, contents)
self.writeToGitStream(git_mode, relPath, contents)
@@ -2906,8 +3114,8 @@ def streamOneP4Deletion(self, file):
if self.largeFileSystem and self.largeFileSystem.isLargeFile(relPath):
self.largeFileSystem.removeLargeFile(relPath)
- # handle another chunk of streaming data
def streamP4FilesCb(self, marshalled):
+ """Handle another chunk of streaming data."""
# catch p4 errors and complain
err = None
@@ -2958,9 +3166,9 @@ def streamP4FilesCb(self, marshalled):
self.stream_file[k] = marshalled[k]
if (verbose and
- 'streamContentSize' in self.stream_file and
- 'fileSize' in self.stream_file and
- 'depotFile' in self.stream_file):
+ 'streamContentSize' in self.stream_file and
+ 'fileSize' in self.stream_file and
+ 'depotFile' in self.stream_file):
size = int(self.stream_file["fileSize"])
if size > 0:
progress = 100*self.stream_file['streamContentSize']/size
@@ -2971,8 +3179,9 @@ def streamP4FilesCb(self, marshalled):
self.stream_have_file_info = True
- # Stream directly from "p4 files" into "git fast-import"
def streamP4Files(self, files):
+ """Stream directly from "p4 files" into "git fast-import."""
+
filesForCommit = []
filesToRead = []
filesToDelete = []
@@ -3023,9 +3232,10 @@ def make_email(self, userid):
return "%s <a@b>" % userid
def streamTag(self, gitStream, labelName, labelDetails, commit, epoch):
- """ Stream a p4 tag.
- commit is either a git commit, or a fast-import mark, ":<p4commit>"
- """
+ """Stream a p4 tag.
+
+ Commit is either a git commit, or a fast-import mark, ":<p4commit>".
+ """
if verbose:
print("writing tag %s for commit %s" % (labelName, commit))
@@ -3047,7 +3257,7 @@ def streamTag(self, gitStream, labelName, labelDetails, commit, epoch):
gitStream.write("tagger %s\n" % tagger)
- print("labelDetails=",labelDetails)
+ print("labelDetails=", labelDetails)
if 'Description' in labelDetails:
description = labelDetails['Description']
else:
@@ -3075,15 +3285,18 @@ def hasBranchPrefix(self, path):
return hasPrefix
def findShadowedFiles(self, files, change):
- # Perforce allows you commit files and directories with the same name,
- # so you could have files //depot/foo and //depot/foo/bar both checked
- # in. A p4 sync of a repository in this state fails. Deleting one of
- # the files recovers the repository.
- #
- # Git will not allow the broken state to exist and only the most recent
- # of the conflicting names is left in the repository. When one of the
- # conflicting files is deleted we need to re-add the other one to make
- # sure the git repository recovers in the same way as perforce.
+ """Perforce allows you commit files and directories with the same name,
+ so you could have files //depot/foo and //depot/foo/bar both checked
+ in. A p4 sync of a repository in this state fails. Deleting one of
+ the files recovers the repository.
+
+ Git will not allow the broken state to exist and only the most
+ recent of the conflicting names is left in the repository. When one
+ of the conflicting files is deleted we need to re-add the other one
+ to make sure the git repository recovers in the same way as
+ perforce.
+ """
+
deleted = [f for f in files if f['action'] in self.delete_actions]
to_check = set()
for f in deleted:
@@ -3110,7 +3323,7 @@ def findShadowedFiles(self, files, change):
'rev': record['headRev'],
'type': record['headType']})
- def commit(self, details, files, branch, parent = "", allow_empty=False):
+ def commit(self, details, files, branch, parent="", allow_empty=False):
epoch = details["time"]
author = details["user"]
jobs = self.extractJobsFromCommit(details)
@@ -3200,8 +3413,11 @@ def commit(self, details, files, branch, parent = "", allow_empty=False):
print("Tag %s does not match with change %s: file count is different."
% (labelDetails["label"], change))
- # Build a dictionary of changelists and labels, for "detect-labels" option.
def getLabels(self):
+ """Build a dictionary of changelists and labels, for "detect-labels"
+ option.
+ """
+
self.labels = {}
l = p4CmdList(["labels"] + ["%s..." % p for p in self.depotPaths])
@@ -3227,11 +3443,12 @@ def getLabels(self):
if self.verbose:
print("Label changes: %s" % self.labels.keys())
- # Import p4 labels as git tags. A direct mapping does not
- # exist, so assume that if all the files are at the same revision
- # then we can use that, or it's something more complicated we should
- # just ignore.
def importP4Labels(self, stream, p4Labels):
+ """Import p4 labels as git tags. A direct mapping does not exist, so
+ assume that if all the files are at the same revision then we can
+ use that, or it's something more complicated we should just ignore.
+ """
+
if verbose:
print("import p4 labels: " + ' '.join(p4Labels))
@@ -3246,7 +3463,7 @@ def importP4Labels(self, stream, p4Labels):
if not m.match(name):
if verbose:
- print("label %s does not match regexp %s" % (name,validLabelRegexp))
+ print("label %s does not match regexp %s" % (name, validLabelRegexp))
continue
if name in ignoredP4Labels:
@@ -3302,7 +3519,7 @@ def guessProjectName(self):
p = p[:-1]
p = p[p.strip().rfind("/") + 1:]
if not p.endswith("/"):
- p += "/"
+ p += "/"
return p
def getBranchMapping(self):
@@ -3322,7 +3539,7 @@ def getBranchMapping(self):
continue
source = paths[0]
destination = paths[1]
- ## HACK
+ # HACK
if p4PathStartsWith(source, self.depotPaths[0]) and p4PathStartsWith(destination, self.depotPaths[0]):
source = source[len(self.depotPaths[0]):-4]
destination = destination[len(self.depotPaths[0]):-4]
@@ -3351,7 +3568,7 @@ def getBranchMapping(self):
configBranches = gitConfigList("git-p4.branchList")
for branch in configBranches:
if branch:
- (source, destination) = branch.split(":")
+ source, destination = branch.split(":")
self.knownBranches[destination] = source
lostAndFoundBranches.discard(destination)
@@ -3359,7 +3576,6 @@ def getBranchMapping(self):
if source not in self.knownBranches:
lostAndFoundBranches.add(source)
-
for branch in lostAndFoundBranches:
self.knownBranches[branch] = branch
@@ -3431,27 +3647,22 @@ def gitCommitByP4Change(self, ref, change):
def importNewBranch(self, branch, maxChange):
# make fast-import flush all changes to disk and update the refs using the checkpoint
# command so that we can try to find the branch parent in the git history
- self.gitStream.write("checkpoint\n\n");
- self.gitStream.flush();
+ self.gitStream.write("checkpoint\n\n")
+ self.gitStream.flush()
branchPrefix = self.depotPaths[0] + branch + "/"
range = "@1,%s" % maxChange
- #print "prefix" + branchPrefix
changes = p4ChangesForPaths([branchPrefix], range, self.changes_block_size)
if len(changes) <= 0:
return False
firstChange = changes[0]
- #print "first change in branch: %s" % firstChange
sourceBranch = self.knownBranches[branch]
sourceDepotPath = self.depotPaths[0] + sourceBranch
sourceRef = self.gitRefForBranch(sourceBranch)
- #print "source " + sourceBranch
branchParentChange = int(p4Cmd(["changes", "-m", "1", "%s...@1,%s" % (sourceDepotPath, firstChange)])["change"])
- #print "branch parent: %s" % branchParentChange
gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
if len(gitParent) > 0:
self.initialParents[self.gitRefForBranch(branch)] = gitParent
- #print "parent git commit: %s" % gitParent
self.importChanges(changes)
return True
@@ -3486,9 +3697,9 @@ def importChanges(self, changes, origin_revision=0):
if self.detectBranches:
branches = self.splitFilesIntoBranches(description)
for branch in branches.keys():
- ## HACK --hwn
+ # HACK --hwn
branchPrefix = self.depotPaths[0] + branch + "/"
- self.branchPrefixes = [ branchPrefix ]
+ self.branchPrefixes = [branchPrefix]
parent = ""
@@ -3508,12 +3719,12 @@ def importChanges(self, changes, origin_revision=0):
fullBranch = self.projectName + branch
if fullBranch not in self.p4BranchesInGit:
if not self.silent:
- print("\n Importing new branch %s" % fullBranch);
+ print("\n Importing new branch %s" % fullBranch)
if self.importNewBranch(branch, change - 1):
parent = ""
self.p4BranchesInGit.append(fullBranch)
if not self.silent:
- print("\n Resuming with change %s" % change);
+ print("\n Resuming with change %s" % change)
if self.verbose:
print("parent determined through known branches: %s" % parent)
@@ -3572,7 +3783,7 @@ def importHeadRevision(self, revision):
newestRevision = 0
fileCnt = 0
- fileArgs = ["%s...%s" % (p,revision) for p in self.depotPaths]
+ fileArgs = ["%s...%s" % (p, revision) for p in self.depotPaths]
for info in p4CmdList(["files"] + fileArgs):
@@ -3581,24 +3792,21 @@ def importHeadRevision(self, revision):
% info['data'])
if info['data'].find("must refer to client") >= 0:
sys.stderr.write("This particular p4 error is misleading.\n")
- sys.stderr.write("Perhaps the depot path was misspelled.\n");
+ sys.stderr.write("Perhaps the depot path was misspelled.\n")
sys.stderr.write("Depot path: %s\n" % " ".join(self.depotPaths))
sys.exit(1)
if 'p4ExitCode' in info:
sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode'])
sys.exit(1)
-
change = int(info["change"])
if change > newestRevision:
newestRevision = change
if info["action"] in self.delete_actions:
- # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
- #fileCnt = fileCnt + 1
continue
- for prop in ["depotFile", "rev", "action", "type" ]:
+ for prop in ["depotFile", "rev", "action", "type"]:
details["%s%s" % (prop, fileCnt)] = info[prop]
fileCnt = fileCnt + 1
@@ -3618,7 +3826,6 @@ def importHeadRevision(self, revision):
print("IO error details: {}".format(err))
print(self.gitError.read())
-
def importRevisions(self, args, branch_arg_given):
changes = []
@@ -3690,7 +3897,7 @@ def openStreams(self):
self.importProcess = subprocess.Popen(["git", "fast-import"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
- stderr=subprocess.PIPE);
+ stderr=subprocess.PIPE)
self.gitOutput = self.importProcess.stdout
self.gitStream = self.importProcess.stdin
self.gitError = self.importProcess.stderr
@@ -3750,9 +3957,13 @@ def run(self, args):
# restrict to just this one, disabling detect-branches
if branch_arg_given:
- short = self.branch.split("/")[-1]
+ short = shortP4Ref(self.branch, self.importIntoRemotes)
if short in branches:
- self.p4BranchesInGit = [ short ]
+ self.p4BranchesInGit = [short]
+ elif self.branch.startswith('refs/') and \
+ branchExists(self.branch) and \
+ '[git-p4:' in extractLogMessageFromGitCommit(self.branch):
+ self.p4BranchesInGit = [self.branch]
else:
self.p4BranchesInGit = branches.keys()
@@ -3769,13 +3980,13 @@ def run(self, args):
p4Change = 0
for branch in self.p4BranchesInGit:
- logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
+ logMsg = extractLogMessageFromGitCommit(fullP4Ref(branch,
+ self.importIntoRemotes))
settings = extractSettingsGitLog(logMsg)
self.readOptions(settings)
- if ('depot-paths' in settings
- and 'change' in settings):
+ if 'depot-paths' in settings and 'change' in settings:
change = int(settings['change']) + 1
p4Change = max(p4Change, change)
@@ -3792,7 +4003,7 @@ def run(self, args):
i = i - 1
break
- paths.append ("/".join(cur_list[:i + 1]))
+ paths.append("/".join(cur_list[:i + 1]))
self.previousDepotPaths = paths
@@ -3802,18 +4013,7 @@ def run(self, args):
if not self.silent and not self.detectBranches:
print("Performing incremental import into %s git branch" % self.branch)
- # accept multiple ref name abbreviations:
- # refs/foo/bar/branch -> use it exactly
- # p4/branch -> prepend refs/remotes/ or refs/heads/
- # branch -> prepend refs/remotes/p4/ or refs/heads/p4/
- if not self.branch.startswith("refs/"):
- if self.importIntoRemotes:
- prepend = "refs/remotes/"
- else:
- prepend = "refs/heads/"
- if not self.branch.startswith("p4/"):
- prepend += "p4/"
- self.branch = prepend + self.branch
+ self.branch = fullP4Ref(self.branch, self.importIntoRemotes)
if len(args) == 0 and self.depotPaths:
if not self.silent:
@@ -3821,8 +4021,8 @@ def run(self, args):
else:
if self.depotPaths and self.depotPaths != args:
print("previous import used depot path %s and now %s was specified. "
- "This doesn't work!" % (' '.join (self.depotPaths),
- ' '.join (args)))
+ "This doesn't work!" % (' '.join(self.depotPaths),
+ ' '.join(args)))
sys.exit(1)
self.depotPaths = sorted(args)
@@ -3862,7 +4062,7 @@ def run(self, args):
if len(self.changesFile) == 0:
revision = "#head"
- p = re.sub ("\.\.\.$", "", p)
+ p = re.sub("\.\.\.$", "", p)
if not p.endswith("/"):
p += "/"
@@ -3876,10 +4076,10 @@ def run(self, args):
self.loadUserMapFromCache()
self.labels = {}
if self.detectLabels:
- self.getLabels();
+ self.getLabels()
if self.detectBranches:
- ## FIXME - what's a P4 projectName ?
+ # FIXME - what's a P4 projectName ?
self.projectName = self.guessProjectName()
if self.hasOrigin:
@@ -3892,7 +4092,7 @@ def run(self, args):
for b in self.p4BranchesInGit:
if b != "master":
- ## FIXME
+ # FIXME
b = b[len(self.projectName):]
self.createdBranches.add(b)
@@ -3942,6 +4142,7 @@ def run(self, args):
return True
+
class P4Rebase(Command):
def __init__(self):
Command.__init__(self)
@@ -3961,11 +4162,11 @@ def run(self, args):
def rebase(self):
if os.system("git update-index --refresh") != 0:
- die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up to date or stash away all your changes with git stash.");
+ die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up to date or stash away all your changes with git stash.")
if len(read_pipe(["git", "diff-index", "HEAD", "--"])) > 0:
- die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash.");
+ die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash.")
- [upstream, settings] = findUpstreamBranchPoint()
+ upstream, settings = findUpstreamBranchPoint()
if len(upstream) == 0:
die("Cannot find upstream branchpoint for rebase")
@@ -3979,6 +4180,7 @@ def rebase(self):
"HEAD", "--"])
return True
+
class P4Clone(P4Sync):
def __init__(self):
P4Sync.__init__(self)
@@ -3996,7 +4198,7 @@ def __init__(self):
self.cloneBare = False
def defaultDestination(self, args):
- ## TODO: use common prefix of args?
+ # TODO: use common prefix of args?
depotPath = args[0]
depotDir = re.sub("(@[^@]*)$", "", depotPath)
depotDir = re.sub("(#[^#]*)$", "", depotDir)
@@ -4032,7 +4234,7 @@ def run(self, args):
os.makedirs(self.cloneDestination)
chdir(self.cloneDestination)
- init_cmd = [ "git", "init" ]
+ init_cmd = ["git", "init"]
if self.cloneBare:
init_cmd.append("--bare")
retcode = subprocess.call(init_cmd)
@@ -4044,11 +4246,11 @@ def run(self, args):
# create a master branch and check out a work tree
if gitBranchExists(self.branch):
- system([ "git", "branch", currentGitBranch(), self.branch ])
+ system(["git", "branch", currentGitBranch(), self.branch])
if not self.cloneBare:
- system([ "git", "checkout", "-f" ])
+ system(["git", "checkout", "-f"])
else:
- print('Not checking out any branch, use ' \
+ print('Not checking out any branch, use '
'"git checkout -q -b master <branch>"')
# auto-set this variable if invoked with --use-client-spec
@@ -4057,6 +4259,7 @@ def run(self, args):
return True
+
class P4Unshelve(Command):
def __init__(self):
Command.__init__(self)
@@ -4073,14 +4276,14 @@ def __init__(self):
self.destbranch = "refs/remotes/p4-unshelved"
def renameBranch(self, branch_name):
- """ Rename the existing branch to branch_name.N
- """
+ """Rename the existing branch to branch_name.N ."""
found = True
- for i in range(0,1000):
+ for i in range(0, 1000):
backup_branch_name = "{0}.{1}".format(branch_name, i)
if not gitBranchExists(backup_branch_name):
- gitUpdateRef(backup_branch_name, branch_name) # copy ref to backup
+ # Copy ref to backup
+ gitUpdateRef(backup_branch_name, branch_name)
gitDeleteRef(branch_name)
found = True
print("renamed old unshelve branch to {0}".format(backup_branch_name))
@@ -4090,9 +4293,9 @@ def renameBranch(self, branch_name):
sys.exit("gave up trying to rename existing branch {0}".format(sync.branch))
def findLastP4Revision(self, starting_point):
- """ Look back from starting_point for the first commit created by git-p4
- to find the P4 commit we are based on, and the depot-paths.
- """
+ """Look back from starting_point for the first commit created by git-p4
+ to find the P4 commit we are based on, and the depot-paths.
+ """
for parent in (range(65535)):
log = extractLogMessageFromGitCommit("{0}~{1}".format(starting_point, parent))
@@ -4103,8 +4306,9 @@ def findLastP4Revision(self, starting_point):
sys.exit("could not find git-p4 commits in {0}".format(self.origin))
def createShelveParent(self, change, branch_name, sync, origin):
- """ Create a commit matching the parent of the shelved changelist 'change'
- """
+ """Create a commit matching the parent of the shelved changelist
+ 'change'.
+ """
parent_description = p4_describe(change, shelved=True)
parent_description['desc'] = 'parent for shelved changelist {}\n'.format(change)
files = sync.extractFilesFromCommit(parent_description, shelved=False, shelved_cl=change)
@@ -4172,10 +4376,11 @@ def run(self, args):
return True
+
class P4Branches(Command):
def __init__(self):
Command.__init__(self)
- self.options = [ ]
+ self.options = []
self.description = ("Shows the git branches that hold imports and their "
+ "corresponding perforce depot paths")
self.verbose = False
@@ -4197,6 +4402,7 @@ def run(self, args):
print("%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"]))
return True
+
class HelpFormatter(optparse.IndentedHelpFormatter):
def __init__(self):
optparse.IndentedHelpFormatter.__init__(self)
@@ -4207,6 +4413,7 @@ def format_description(self, description):
else:
return ""
+
def printUsage(commands):
print("usage: %s <command> [options]" % sys.argv[0])
print("")
@@ -4215,16 +4422,18 @@ def printUsage(commands):
print("Try %s <command> --help for command specific help." % sys.argv[0])
print("")
+
commands = {
- "submit" : P4Submit,
- "commit" : P4Submit,
- "sync" : P4Sync,
- "rebase" : P4Rebase,
- "clone" : P4Clone,
- "branches" : P4Branches,
- "unshelve" : P4Unshelve,
+ "submit": P4Submit,
+ "commit": P4Submit,
+ "sync": P4Sync,
+ "rebase": P4Rebase,
+ "clone": P4Clone,
+ "branches": P4Branches,
+ "unshelve": P4Unshelve,
}
+
def main():
if len(sys.argv[1:]) == 0:
printUsage(commands.keys())
@@ -4251,11 +4460,11 @@ def main():
parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
options,
- description = cmd.description,
- formatter = HelpFormatter())
+ description=cmd.description,
+ formatter=HelpFormatter())
try:
- (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
+ cmd, args = parser.parse_args(sys.argv[2:], cmd)
except:
parser.print_help()
raise
@@ -4263,7 +4472,7 @@ def main():
global verbose
verbose = cmd.verbose
if cmd.needsGit:
- if cmd.gitdir == None:
+ if cmd.gitdir is None:
cmd.gitdir = os.path.abspath(".git")
if not isValidGitDir(cmd.gitdir):
# "rev-parse --git-dir" without arguments will try $PWD/.git
@@ -4271,7 +4480,7 @@ def main():
if os.path.exists(cmd.gitdir):
cdup = read_pipe(["git", "rev-parse", "--show-cdup"]).strip()
if len(cdup) > 0:
- chdir(cdup);
+ chdir(cdup)
if not isValidGitDir(cmd.gitdir):
if isValidGitDir(cmd.gitdir + "/.git"):
diff --git a/git.c b/git.c
index 3d8e48c..5ff4f3e 100644
--- a/git.c
+++ b/git.c
@@ -25,7 +25,7 @@ struct cmd_struct {
};
const char git_usage_string[] =
- N_("git [--version] [--help] [-C <path>] [-c <name>=<value>]\n"
+ N_("git [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]\n"
" [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
" [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\n"
" [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
@@ -146,7 +146,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
* commands can be written with "--" prepended
* to make them look like flags.
*/
- if (!strcmp(cmd, "--help") || !strcmp(cmd, "--version"))
+ if (!strcmp(cmd, "--help") || !strcmp(cmd, "-h") ||
+ !strcmp(cmd, "--version") || !strcmp(cmd, "-v"))
break;
/*
@@ -893,8 +894,10 @@ int cmd_main(int argc, const char **argv)
argc--;
handle_options(&argv, &argc, NULL);
if (argc > 0) {
- /* translate --help and --version into commands */
- skip_prefix(argv[0], "--", &argv[0]);
+ if (!strcmp("--version", argv[0]) || !strcmp("-v", argv[0]))
+ argv[0] = "version";
+ else if (!strcmp("--help", argv[0]) || !strcmp("-h", argv[0]))
+ argv[0] = "help";
} else {
/* The user didn't specify a command; give them help */
commit_pager_choice();
diff --git a/gitk-git/gitk b/gitk-git/gitk
index 23d9dd1..0ae7d68 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -2955,9 +2955,9 @@
proc resizeclistpanes {win w} {
global oldwidth oldsash use_ttk
if {[info exists oldwidth($win)]} {
- if {[info exists oldsash($win)]} {
- set s0 [lindex $oldsash($win) 0]
- set s1 [lindex $oldsash($win) 1]
+ if {[info exists oldsash($win)]} {
+ set s0 [lindex $oldsash($win) 0]
+ set s1 [lindex $oldsash($win) 1]
} elseif {$use_ttk} {
set s0 [$win sashpos 0]
set s1 [$win sashpos 1]
@@ -2991,8 +2991,10 @@
} else {
$win sash place 0 $sash0 [lindex $s0 1]
$win sash place 1 $sash1 [lindex $s1 1]
+ set sash0 [list $sash0 [lindex $s0 1]]
+ set sash1 [list $sash1 [lindex $s1 1]]
}
- set oldsash($win) [list $sash0 $sash1]
+ set oldsash($win) [list $sash0 $sash1]
}
set oldwidth($win) $w
}
@@ -3000,8 +3002,8 @@
proc resizecdetpanes {win w} {
global oldwidth oldsash use_ttk
if {[info exists oldwidth($win)]} {
- if {[info exists oldsash($win)]} {
- set s0 $oldsash($win)
+ if {[info exists oldsash($win)]} {
+ set s0 $oldsash($win)
} elseif {$use_ttk} {
set s0 [$win sashpos 0]
} else {
@@ -3023,8 +3025,9 @@
$win sashpos 0 $sash0
} else {
$win sash place 0 $sash0 [lindex $s0 1]
+ set sash0 [list $sash0 [lindex $s0 1]]
}
- set oldsash($win) $sash0
+ set oldsash($win) $sash0
}
set oldwidth($win) $w
}
diff --git a/log-tree.c b/log-tree.c
index 38e5ccc..3a03e34 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -1098,6 +1098,7 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)
opt->loginfo = &log;
opt->diffopt.no_free = 1;
+ /* NEEDSWORK: no restoring of no_free? Why? */
if (opt->line_level_traverse)
return line_log_print(opt, commit);
diff --git a/mergetools/araxis b/mergetools/araxis
index e2407b6..eb32a7d 100644
--- a/mergetools/araxis
+++ b/mergetools/araxis
@@ -2,6 +2,10 @@
"$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" >/dev/null 2>&1
}
+diff_cmd_help () {
+ echo "Use Araxis Merge (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -13,6 +17,10 @@
fi
}
+merge_cmd_help () {
+ echo "Use Araxis Merge (requires a graphical session)"
+}
+
translate_merge_tool_path() {
echo compare
}
diff --git a/mergetools/bc b/mergetools/bc
index 26c19d4..2922667 100644
--- a/mergetools/bc
+++ b/mergetools/bc
@@ -2,6 +2,10 @@
"$merge_tool_path" "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use Beyond Compare (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -13,6 +17,10 @@
fi
}
+merge_cmd_help () {
+ echo "Use Beyond Compare (requires a graphical session)"
+}
+
translate_merge_tool_path() {
if type bcomp >/dev/null 2>/dev/null
then
diff --git a/mergetools/codecompare b/mergetools/codecompare
index 9f60e8d..610963d 100644
--- a/mergetools/codecompare
+++ b/mergetools/codecompare
@@ -2,6 +2,10 @@
"$merge_tool_path" "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use Code Compare (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -13,6 +17,10 @@
fi
}
+merge_cmd_help () {
+ echo "Use Code Compare (requires a graphical session)"
+}
+
translate_merge_tool_path() {
if merge_mode
then
diff --git a/mergetools/deltawalker b/mergetools/deltawalker
index ee6f374..efae4c2 100644
--- a/mergetools/deltawalker
+++ b/mergetools/deltawalker
@@ -2,6 +2,10 @@
"$merge_tool_path" "$LOCAL" "$REMOTE" >/dev/null 2>&1
}
+diff_cmd_help () {
+ echo "Use DeltaWalker (requires a graphical session)"
+}
+
merge_cmd () {
# Adding $(pwd)/ in front of $MERGED should not be necessary.
# However without it, DeltaWalker (at least v1.9.8 on Windows)
@@ -16,6 +20,10 @@
fi >/dev/null 2>&1
}
+merge_cmd_help () {
+ echo "Use DeltaWalker (requires a graphical session)"
+}
+
translate_merge_tool_path () {
echo DeltaWalker
}
diff --git a/mergetools/diffmerge b/mergetools/diffmerge
index 9b6355b..9b5b62d 100644
--- a/mergetools/diffmerge
+++ b/mergetools/diffmerge
@@ -2,6 +2,10 @@
"$merge_tool_path" "$LOCAL" "$REMOTE" >/dev/null 2>&1
}
+diff_cmd_help () {
+ echo "Use DiffMerge (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -13,6 +17,10 @@
fi
}
+merge_cmd_help () {
+ echo "Use DiffMerge (requires a graphical session)"
+}
+
exit_code_trustable () {
true
}
diff --git a/mergetools/diffuse b/mergetools/diffuse
index 5a3ae8b..ebfaba5 100644
--- a/mergetools/diffuse
+++ b/mergetools/diffuse
@@ -2,6 +2,10 @@
"$merge_tool_path" "$LOCAL" "$REMOTE" | cat
}
+diff_cmd_help () {
+ echo "Use Diffuse (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -13,3 +17,7 @@
"$LOCAL" "$MERGED" "$REMOTE" | cat
fi
}
+
+merge_cmd_help () {
+ echo "Use Diffuse (requires a graphical session)"
+}
diff --git a/mergetools/ecmerge b/mergetools/ecmerge
index 6c5101c..0d4d609 100644
--- a/mergetools/ecmerge
+++ b/mergetools/ecmerge
@@ -2,6 +2,10 @@
"$merge_tool_path" --default --mode=diff2 "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use ECMerge (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -12,3 +16,7 @@
--default --mode=merge2 --to="$MERGED"
fi
}
+
+merge_cmd_help () {
+ echo "Use ECMerge (requires a graphical session)"
+}
diff --git a/mergetools/emerge b/mergetools/emerge
index d1ce513..fc6892c 100644
--- a/mergetools/emerge
+++ b/mergetools/emerge
@@ -2,6 +2,10 @@
"$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use Emacs' Emerge"
+}
+
merge_cmd () {
if $base_present
then
@@ -17,6 +21,10 @@
fi
}
+merge_cmd_help () {
+ echo "Use Emacs' Emerge"
+}
+
translate_merge_tool_path() {
echo emacs
}
diff --git a/mergetools/examdiff b/mergetools/examdiff
index e72b06f..6f53ca9 100644
--- a/mergetools/examdiff
+++ b/mergetools/examdiff
@@ -2,6 +2,10 @@
"$merge_tool_path" "$LOCAL" "$REMOTE" -nh
}
+diff_cmd_help () {
+ echo "Use ExamDiff Pro (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -11,6 +15,10 @@
fi
}
+merge_cmd_help () {
+ echo "Use ExamDiff Pro (requires a graphical session)"
+}
+
translate_merge_tool_path() {
mergetool_find_win32_cmd "ExamDiff.com" "ExamDiff Pro"
}
diff --git a/mergetools/guiffy b/mergetools/guiffy
index 8b23a13..3ed07ef 100644
--- a/mergetools/guiffy
+++ b/mergetools/guiffy
@@ -2,6 +2,10 @@
"$merge_tool_path" "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use Guiffy's Diff Tool (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -13,6 +17,10 @@
fi
}
+merge_cmd_help () {
+ echo "Use Guiffy's Diff Tool (requires a graphical session)"
+}
+
exit_code_trustable () {
true
}
diff --git a/mergetools/kdiff3 b/mergetools/kdiff3
index 520cb91..ee8b3a05 100644
--- a/mergetools/kdiff3
+++ b/mergetools/kdiff3
@@ -4,6 +4,10 @@
"$LOCAL" "$REMOTE" >/dev/null 2>&1
}
+diff_cmd_help () {
+ echo "Use KDiff3 (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -22,6 +26,10 @@
fi
}
+merge_cmd_help () {
+ echo "Use KDiff3 (requires a graphical session)"
+}
+
exit_code_trustable () {
true
}
diff --git a/mergetools/kompare b/mergetools/kompare
index e8c0bfa..4ce23db 100644
--- a/mergetools/kompare
+++ b/mergetools/kompare
@@ -2,10 +2,18 @@
return 1
}
+diff_cmd_help () {
+ echo "Use Kompare (requires a graphical session)"
+}
+
diff_cmd () {
"$merge_tool_path" "$LOCAL" "$REMOTE"
}
+merge_cmd_help () {
+ echo "Use Kompare (requires a graphical session)"
+}
+
exit_code_trustable () {
true
}
diff --git a/mergetools/meld b/mergetools/meld
index aab4ebb..8ec0867 100644
--- a/mergetools/meld
+++ b/mergetools/meld
@@ -2,6 +2,10 @@
"$merge_tool_path" "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use Meld (requires a graphical session)"
+}
+
merge_cmd () {
check_meld_for_features
@@ -20,6 +24,10 @@
fi
}
+merge_cmd_help () {
+ echo "Use Meld (requires a graphical session) with optional \`auto merge\` (see \`git help mergetool\`'s \`CONFIGURATION\` section)"
+}
+
# Get meld help message
init_meld_help_msg () {
if test -z "$meld_help_msg"
diff --git a/mergetools/opendiff b/mergetools/opendiff
index b608dd6..44adf8f 100644
--- a/mergetools/opendiff
+++ b/mergetools/opendiff
@@ -2,6 +2,10 @@
"$merge_tool_path" "$LOCAL" "$REMOTE" | cat
}
+diff_cmd_help () {
+ echo "Use FileMerge (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -12,3 +16,7 @@
-merge "$MERGED" | cat
fi
}
+
+merge_cmd_help () {
+ echo "Use FileMerge (requires a graphical session)"
+}
diff --git a/mergetools/p4merge b/mergetools/p4merge
index 7a5b291..f3cb197 100644
--- a/mergetools/p4merge
+++ b/mergetools/p4merge
@@ -19,6 +19,10 @@
fi
}
+diff_cmd_help () {
+ echo "Use HelixCore P4Merge (requires a graphical session)"
+}
+
merge_cmd () {
if ! $base_present
then
@@ -34,3 +38,7 @@
printf "%s" "$empty_file"
}
+
+merge_cmd_help () {
+ echo "Use HelixCore P4Merge (requires a graphical session)"
+}
diff --git a/mergetools/smerge b/mergetools/smerge
index 9c2e6f6..5410835 100644
--- a/mergetools/smerge
+++ b/mergetools/smerge
@@ -2,6 +2,10 @@
"$merge_tool_path" mergetool "$LOCAL" "$REMOTE" -o "$MERGED"
}
+diff_cmd_help () {
+ echo "Use Sublime Merge (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -10,3 +14,7 @@
"$merge_tool_path" mergetool "$LOCAL" "$REMOTE" -o "$MERGED"
fi
}
+
+merge_cmd_help () {
+ echo "Use Sublime Merge (requires a graphical session)"
+}
diff --git a/mergetools/tkdiff b/mergetools/tkdiff
index eee5cb5..66906a7 100644
--- a/mergetools/tkdiff
+++ b/mergetools/tkdiff
@@ -2,6 +2,10 @@
"$merge_tool_path" "$LOCAL" "$REMOTE"
}
+diff_cmd_help () {
+ echo "Use TkDiff (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -14,3 +18,7 @@
exit_code_trustable () {
true
}
+
+merge_cmd_help () {
+ echo "Use TkDiff (requires a graphical session)"
+}
diff --git a/mergetools/tortoisemerge b/mergetools/tortoisemerge
index d7ab666..507edcd 100644
--- a/mergetools/tortoisemerge
+++ b/mergetools/tortoisemerge
@@ -2,6 +2,10 @@
return 1
}
+diff_cmd_help () {
+ echo "Use TortoiseMerge (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -30,3 +34,7 @@
echo tortoisemerge
fi
}
+
+merge_cmd_help () {
+ echo "Use TortoiseMerge (requires a graphical session)"
+}
diff --git a/mergetools/vimdiff b/mergetools/vimdiff
index 96f6209..461a89b 100644
--- a/mergetools/vimdiff
+++ b/mergetools/vimdiff
@@ -1,49 +1,495 @@
+# This script can be run in two different contexts:
+#
+# - From git, when the user invokes the "vimdiff" merge tool. In this context
+# this script expects the following environment variables (among others) to
+# be defined (which is something "git" takes care of):
+#
+# - $BASE
+# - $LOCAL
+# - $REMOTE
+# - $MERGED
+#
+# In this mode, all this script does is to run the next command:
+#
+# vim -f -c ... $LOCAL $BASE $REMOTE $MERGED
+#
+# ...where the "..." string depends on the value of the
+# "mergetool.vimdiff.layout" configuration variable and is used to open vim
+# with a certain layout of buffers, windows and tabs.
+#
+# - From a script inside the unit tests framework folder ("t" folder) by
+# sourcing this script and then manually calling "run_unit_tests", which
+# will run a battery of unit tests to make sure nothing breaks.
+# In this context this script does not expect any particular environment
+# variable to be set.
+
+
+################################################################################
+## Internal functions (not meant to be used outside this script)
+################################################################################
+
+debug_print () {
+ # Send message to stderr if global variable GIT_MERGETOOL_VIMDIFF is set
+ # to "true"
+
+ if test -n "$GIT_MERGETOOL_VIMDIFF_DEBUG"
+ then
+ >&2 echo "$@"
+ fi
+}
+
+substring () {
+ # Return a substring of $1 containing $3 characters starting at
+ # zero-based offset $2.
+ #
+ # Examples:
+ #
+ # substring "Hello world" 0 4 --> "Hell"
+ # substring "Hello world" 3 4 --> "lo w"
+ # substring "Hello world" 3 10 --> "lo world"
+
+ STRING=$1
+ START=$2
+ LEN=$3
+
+ echo "$STRING" | cut -c$(( START + 1 ))-$(( START + $LEN ))
+}
+
+gen_cmd_aux () {
+ # Auxiliary function used from "gen_cmd()".
+ # Read that other function documentation for more details.
+
+ LAYOUT=$1
+ CMD=$2 # This is a second (hidden) argument used for recursion
+
+ debug_print
+ debug_print "LAYOUT : $LAYOUT"
+ debug_print "CMD : $CMD"
+
+ if test -z "$CMD"
+ then
+ CMD="echo" # vim "nop" operator
+ fi
+
+ start=0
+ end=${#LAYOUT}
+
+ nested=0
+ nested_min=100
+
+
+ # Step 1:
+ #
+ # Increase/decrease "start"/"end" indices respectively to get rid of
+ # outer parenthesis.
+ #
+ # Example:
+ #
+ # - BEFORE: (( LOCAL , BASE ) / MERGED )
+ # - AFTER : ( LOCAL , BASE ) / MERGED
+
+ oldIFS=$IFS
+ IFS=#
+ for c in $(echo "$LAYOUT" | sed 's:.:&#:g')
+ do
+ if test "$c" = " "
+ then
+ continue
+ fi
+
+ if test "$c" = "("
+ then
+ nested=$(( nested + 1 ))
+ continue
+ fi
+
+ if test "$c" = ")"
+ then
+ nested=$(( nested - 1 ))
+ continue
+ fi
+
+ if test "$nested" -lt "$nested_min"
+ then
+ nested_min=$nested
+ fi
+ done
+ IFS=$oldIFS
+
+ debug_print "NESTED MIN: $nested_min"
+
+ while test "$nested_min" -gt "0"
+ do
+ start=$(( start + 1 ))
+ end=$(( end - 1 ))
+
+ start_minus_one=$(( start - 1 ))
+
+ while ! test "$(substring "$LAYOUT" "$start_minus_one" 1)" = "("
+ do
+ start=$(( start + 1 ))
+ start_minus_one=$(( start_minus_one + 1 ))
+ done
+
+ while ! test "$(substring "$LAYOUT" "$end" 1)" = ")"
+ do
+ end=$(( end - 1 ))
+ done
+
+ nested_min=$(( nested_min - 1 ))
+ done
+
+ debug_print "CLEAN : $(substring "$LAYOUT" "$start" "$(( end - start ))")"
+
+
+ # Step 2:
+ #
+ # Search for all valid separators ("+", "/" or ",") which are *not*
+ # inside parenthesis. Save the index at which each of them makes the
+ # first appearance.
+
+ index_new_tab=""
+ index_horizontal_split=""
+ index_vertical_split=""
+
+ nested=0
+ i=$(( start - 1 ))
+
+ oldIFS=$IFS
+ IFS=#
+ for c in $(substring "$LAYOUT" "$start" "$(( end - start ))" | sed 's:.:&#:g');
+ do
+ i=$(( i + 1 ))
+
+ if test "$c" = " "
+ then
+ continue
+ fi
+
+ if test "$c" = "("
+ then
+ nested=$(( nested + 1 ))
+ continue
+ fi
+
+ if test "$c" = ")"
+ then
+ nested=$(( nested - 1 ))
+ continue
+ fi
+
+ if test "$nested" = 0
+ then
+ current=$c
+
+ if test "$current" = "+"
+ then
+ if test -z "$index_new_tab"
+ then
+ index_new_tab=$i
+ fi
+
+ elif test "$current" = "/"
+ then
+ if test -z "$index_horizontal_split"
+ then
+ index_horizontal_split=$i
+ fi
+
+ elif test "$current" = ","
+ then
+ if test -z "$index_vertical_split"
+ then
+ index_vertical_split=$i
+ fi
+ fi
+ fi
+ done
+ IFS=$oldIFS
+
+
+ # Step 3:
+ #
+ # Process the separator with the highest order of precedence
+ # (";" has the highest precedence and "|" the lowest one).
+ #
+ # By "process" I mean recursively call this function twice: the first
+ # one with the substring at the left of the separator and the second one
+ # with the one at its right.
+
+ terminate="false"
+
+ if ! test -z "$index_new_tab"
+ then
+ before="-tabnew"
+ after="tabnext"
+ index=$index_new_tab
+ terminate="true"
+
+ elif ! test -z "$index_horizontal_split"
+ then
+ before="split"
+ after="wincmd j"
+ index=$index_horizontal_split
+ terminate="true"
+
+ elif ! test -z "$index_vertical_split"
+ then
+ before="vertical split"
+ after="wincmd l"
+ index=$index_vertical_split
+ terminate="true"
+ fi
+
+ if test "$terminate" = "true"
+ then
+ CMD="$CMD | $before"
+ CMD=$(gen_cmd_aux "$(substring "$LAYOUT" "$start" "$(( index - start ))")" "$CMD")
+ CMD="$CMD | $after"
+ CMD=$(gen_cmd_aux "$(substring "$LAYOUT" "$(( index + 1 ))" "$(( ${#LAYOUT} - index ))")" "$CMD")
+ echo "$CMD"
+ return
+ fi
+
+
+ # Step 4:
+ #
+ # If we reach this point, it means there are no separators and we just
+ # need to print the command to display the specified buffer
+
+ target=$(substring "$LAYOUT" "$start" "$(( end - start ))" | sed 's:[ @();|-]::g')
+
+ if test "$target" = "LOCAL"
+ then
+ CMD="$CMD | 1b"
+
+ elif test "$target" = "BASE"
+ then
+ CMD="$CMD | 2b"
+
+ elif test "$target" = "REMOTE"
+ then
+ CMD="$CMD | 3b"
+
+ elif test "$target" = "MERGED"
+ then
+ CMD="$CMD | 4b"
+
+ else
+ CMD="$CMD | ERROR: >$target<"
+ fi
+
+ echo "$CMD"
+ return
+}
+
+
+gen_cmd () {
+ # This function returns (in global variable FINAL_CMD) the string that
+ # you can use when invoking "vim" (as shown next) to obtain a given
+ # layout:
+ #
+ # $ vim -f $FINAL_CMD "$LOCAL" "$BASE" "$REMOTE" "$MERGED"
+ #
+ # It takes one single argument: a string containing the desired layout
+ # definition.
+ #
+ # The syntax of the "layout definitions" is explained in "Documentation/
+ # mergetools/vimdiff.txt" but you can already intuitively understand how
+ # it works by knowing that...
+ #
+ # * "+" means "a new vim tab"
+ # * "/" means "a new vim horizontal split"
+ # * "," means "a new vim vertical split"
+ #
+ # It also returns (in global variable FINAL_TARGET) the name ("LOCAL",
+ # "BASE", "REMOTE" or "MERGED") of the file that is marked with an "@",
+ # or "MERGED" if none of them is.
+ #
+ # Example:
+ #
+ # gen_cmd "@LOCAL , REMOTE"
+ # |
+ # `-> FINAL_CMD == "-c \"echo | vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
+ # FINAL_TARGET == "LOCAL"
+
+ LAYOUT=$1
+
+
+ # Search for a "@" in one of the files identifiers ("LOCAL", "BASE",
+ # "REMOTE", "MERGED"). If not found, use "MERGE" as the default file
+ # where changes will be saved.
+
+ if echo "$LAYOUT" | grep @LOCAL >/dev/null
+ then
+ FINAL_TARGET="LOCAL"
+ elif echo "$LAYOUT" | grep @BASE >/dev/null
+ then
+ FINAL_TARGET="BASE"
+ else
+ FINAL_TARGET="MERGED"
+ fi
+
+
+ # Obtain the first part of vim "-c" option to obtain the desired layout
+
+ CMD=$(gen_cmd_aux "$LAYOUT")
+
+
+ # Adjust the just obtained script depending on whether more than one
+ # windows are visible or not
+
+ if echo "$LAYOUT" | grep ",\|/" >/dev/null
+ then
+ CMD="$CMD | tabdo windo diffthis"
+ else
+ CMD="$CMD | bufdo diffthis"
+ fi
+
+
+ # Add an extra "-c" option to move to the first tab (notice that we
+ # can't simply append the command to the previous "-c" string as
+ # explained here: https://github.com/vim/vim/issues/9076
+
+ FINAL_CMD="-c \"$CMD\" -c \"tabfirst\""
+}
+
+
+################################################################################
+## API functions (called from "git-mergetool--lib.sh")
+################################################################################
+
diff_cmd () {
"$merge_tool_path" -R -f -d \
-c 'wincmd l' -c 'cd $GIT_PREFIX' "$LOCAL" "$REMOTE"
}
+
+diff_cmd_help () {
+ TOOL=$1
+
+ case "$TOOL" in
+ nvimdiff*)
+ printf "Use Neovim"
+ ;;
+ gvimdiff*)
+ printf "Use gVim (requires a graphical session)"
+ ;;
+ vimdiff*)
+ printf "Use Vim"
+ ;;
+ esac
+
+ return 0
+}
+
+
merge_cmd () {
+ layout=$(git config mergetool.vimdiff.layout)
+
case "$1" in
*vimdiff)
- if $base_present
+ if test -z "$layout"
then
- "$merge_tool_path" -f -d -c '4wincmd w | wincmd J' \
- "$LOCAL" "$BASE" "$REMOTE" "$MERGED"
- else
- "$merge_tool_path" -f -d -c 'wincmd l' \
- "$LOCAL" "$MERGED" "$REMOTE"
+ # Default layout when none is specified
+ layout="(LOCAL,BASE,REMOTE)/MERGED"
fi
;;
*vimdiff1)
- "$merge_tool_path" -f -d \
- -c 'echon "Resolve conflicts leftward then save. Use :cq to abort."' \
- "$LOCAL" "$REMOTE"
- ret="$?"
- if test "$ret" -eq 0
- then
- cp -- "$LOCAL" "$MERGED"
- fi
- return "$ret"
+ layout="@LOCAL,REMOTE"
;;
*vimdiff2)
- "$merge_tool_path" -f -d -c 'wincmd l' \
- "$LOCAL" "$MERGED" "$REMOTE"
+ layout="LOCAL,MERGED,REMOTE"
;;
*vimdiff3)
- if $base_present
- then
- "$merge_tool_path" -f -d -c 'hid | hid | hid' \
- "$LOCAL" "$REMOTE" "$BASE" "$MERGED"
- else
- "$merge_tool_path" -f -d -c 'hid | hid' \
- "$LOCAL" "$REMOTE" "$MERGED"
- fi
+ layout="MERGED"
;;
esac
+
+ gen_cmd "$layout"
+
+ debug_print ""
+ debug_print "FINAL CMD : $FINAL_CMD"
+ debug_print "FINAL TAR : $FINAL_TARGET"
+
+ if $base_present
+ then
+ eval "$merge_tool_path" \
+ -f "$FINAL_CMD" "$LOCAL" "$BASE" "$REMOTE" "$MERGED"
+ else
+ # If there is no BASE (example: a merge conflict in a new file
+ # with the same name created in both braches which didn't exist
+ # before), close all BASE windows using vim's "quit" command
+
+ FINAL_CMD=$(echo "$FINAL_CMD" | \
+ sed -e 's:2b:quit:g' -e 's:3b:2b:g' -e 's:4b:3b:g')
+
+ eval "$merge_tool_path" \
+ -f "$FINAL_CMD" "$LOCAL" "$REMOTE" "$MERGED"
+ fi
+
+ ret="$?"
+
+ if test "$ret" -eq 0
+ then
+ case "$FINAL_TARGET" in
+ LOCAL)
+ source_path="$LOCAL"
+ ;;
+ REMOTE)
+ source_path="$REMOTE"
+ ;;
+ MERGED|*)
+ # Do nothing
+ source_path=
+ ;;
+ esac
+
+ if test -n "$source_path"
+ then
+ cp "$source_path" "$MERGED"
+ fi
+ fi
+
+ return "$ret"
}
-translate_merge_tool_path() {
+
+merge_cmd_help () {
+ TOOL=$1
+
+ case "$TOOL" in
+ nvimdiff*)
+ printf "Use Neovim "
+ ;;
+ gvimdiff*)
+ printf "Use gVim (requires a graphical session) "
+ ;;
+ vimdiff*)
+ printf "Use Vim "
+ ;;
+ esac
+
+ case "$TOOL" in
+ *1)
+ echo "with a 2 panes layout (LOCAL and REMOTE)"
+ ;;
+ *2)
+ echo "with a 3 panes layout (LOCAL, MERGED and REMOTE)"
+ ;;
+ *3)
+ echo "where only the MERGED file is shown"
+ ;;
+ *)
+ echo "with a custom layout (see \`git help mergetool\`'s \`BACKEND SPECIFIC HINTS\` section)"
+ ;;
+ esac
+
+ return 0
+}
+
+
+translate_merge_tool_path () {
case "$1" in
nvimdiff*)
echo nvim
@@ -57,14 +503,121 @@
esac
}
+
exit_code_trustable () {
true
}
+
list_tool_variants () {
- for prefix in '' g n; do
- for suffix in '' 1 2 3; do
- echo "${prefix}vimdiff${suffix}"
+ if test "$TOOL_MODE" = "diff"
+ then
+ for prefix in '' g n
+ do
+ echo "${prefix}vimdiff"
done
+ else
+ for prefix in '' g n
+ do
+ for suffix in '' 1 2 3
+ do
+ echo "${prefix}vimdiff${suffix}"
+ done
+ done
+ fi
+}
+
+
+################################################################################
+## Unit tests (called from scripts inside the "t" folder)
+################################################################################
+
+run_unit_tests () {
+ # Function to make sure that we don't break anything when modifying this
+ # script.
+
+ NUMBER_OF_TEST_CASES=16
+
+ TEST_CASE_01="(LOCAL,BASE,REMOTE)/MERGED" # default behaviour
+ TEST_CASE_02="@LOCAL,REMOTE" # when using vimdiff1
+ TEST_CASE_03="LOCAL,MERGED,REMOTE" # when using vimdiff2
+ TEST_CASE_04="MERGED" # when using vimdiff3
+ TEST_CASE_05="LOCAL/MERGED/REMOTE"
+ TEST_CASE_06="(LOCAL/REMOTE),MERGED"
+ TEST_CASE_07="MERGED,(LOCAL/REMOTE)"
+ TEST_CASE_08="(LOCAL,REMOTE)/MERGED"
+ TEST_CASE_09="MERGED/(LOCAL,REMOTE)"
+ TEST_CASE_10="(LOCAL/BASE/REMOTE),MERGED"
+ TEST_CASE_11="(LOCAL,BASE,REMOTE)/MERGED+BASE,LOCAL+BASE,REMOTE+(LOCAL/BASE/REMOTE),MERGED"
+ TEST_CASE_12="((LOCAL,REMOTE)/BASE),MERGED"
+ TEST_CASE_13="((LOCAL,REMOTE)/BASE),((LOCAL/REMOTE),MERGED)"
+ TEST_CASE_14="BASE,REMOTE+BASE,LOCAL"
+ TEST_CASE_15=" (( (LOCAL , BASE , REMOTE) / MERGED)) +(BASE) , LOCAL+ BASE , REMOTE+ (((LOCAL / BASE / REMOTE)) , MERGED ) "
+ TEST_CASE_16="LOCAL,BASE,REMOTE / MERGED + BASE,LOCAL + BASE,REMOTE + (LOCAL / BASE / REMOTE),MERGED"
+
+ EXPECTED_CMD_01="-c \"echo | split | vertical split | 1b | wincmd l | vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_02="-c \"echo | vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_03="-c \"echo | vertical split | 1b | wincmd l | vertical split | 4b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_04="-c \"echo | 4b | bufdo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_05="-c \"echo | split | 1b | wincmd j | split | 4b | wincmd j | 3b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_06="-c \"echo | vertical split | split | 1b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_07="-c \"echo | vertical split | 4b | wincmd l | split | 1b | wincmd j | 3b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_08="-c \"echo | split | vertical split | 1b | wincmd l | 3b | wincmd j | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_09="-c \"echo | split | 4b | wincmd j | vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_10="-c \"echo | vertical split | split | 1b | wincmd j | split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_11="-c \"echo | -tabnew | split | vertical split | 1b | wincmd l | vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | vertical split | 2b | wincmd l | 3b | tabnext | vertical split | split | 1b | wincmd j | split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_12="-c \"echo | vertical split | split | vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_13="-c \"echo | vertical split | split | vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | vertical split | split | 1b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_14="-c \"echo | -tabnew | vertical split | 2b | wincmd l | 3b | tabnext | vertical split | 2b | wincmd l | 1b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_15="-c \"echo | -tabnew | split | vertical split | 1b | wincmd l | vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | vertical split | 2b | wincmd l | 3b | tabnext | vertical split | split | 1b | wincmd j | split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+ EXPECTED_CMD_16="-c \"echo | -tabnew | split | vertical split | 1b | wincmd l | vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | vertical split | 2b | wincmd l | 3b | tabnext | vertical split | split | 1b | wincmd j | split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+
+ EXPECTED_TARGET_01="MERGED"
+ EXPECTED_TARGET_02="LOCAL"
+ EXPECTED_TARGET_03="MERGED"
+ EXPECTED_TARGET_04="MERGED"
+ EXPECTED_TARGET_05="MERGED"
+ EXPECTED_TARGET_06="MERGED"
+ EXPECTED_TARGET_07="MERGED"
+ EXPECTED_TARGET_08="MERGED"
+ EXPECTED_TARGET_09="MERGED"
+ EXPECTED_TARGET_10="MERGED"
+ EXPECTED_TARGET_11="MERGED"
+ EXPECTED_TARGET_12="MERGED"
+ EXPECTED_TARGET_13="MERGED"
+ EXPECTED_TARGET_14="MERGED"
+ EXPECTED_TARGET_15="MERGED"
+ EXPECTED_TARGET_16="MERGED"
+
+ at_least_one_ko="false"
+
+ for i in $(seq -w 1 99)
+ do
+ if test "$i" -gt $NUMBER_OF_TEST_CASES
+ then
+ break
+ fi
+
+ gen_cmd "$(eval echo \${TEST_CASE_"$i"})"
+
+ if test "$FINAL_CMD" = "$(eval echo \${EXPECTED_CMD_"$i"})" \
+ && test "$FINAL_TARGET" = "$(eval echo \${EXPECTED_TARGET_"$i"})"
+ then
+ printf "Test Case #%02d: OK\n" "$(echo "$i" | sed 's/^0*//')"
+ else
+ printf "Test Case #%02d: KO !!!!\n" "$(echo "$i" | sed 's/^0*//')"
+ echo " FINAL_CMD : $FINAL_CMD"
+ echo " FINAL_CMD (expected) : $(eval echo \${EXPECTED_CMD_"$i"})"
+ echo " FINAL_TARGET : $FINAL_TARGET"
+ echo " FINAL_TARGET (expected): $(eval echo \${EXPECTED_TARGET_"$i"})"
+ at_least_one_ko="true"
+ fi
done
+
+ if test "$at_least_one_ko" = "true"
+ then
+ return 255
+ else
+ return 0
+ fi
}
diff --git a/mergetools/winmerge b/mergetools/winmerge
index 74d0325..36c72dd 100644
--- a/mergetools/winmerge
+++ b/mergetools/winmerge
@@ -3,6 +3,10 @@
return 0
}
+diff_cmd_help () {
+ echo "Use WinMerge (requires a graphical session)"
+}
+
merge_cmd () {
# mergetool.winmerge.trustExitCode is implicitly false.
# touch $BACKUP so that we can check_unchanged.
@@ -13,3 +17,7 @@
translate_merge_tool_path() {
mergetool_find_win32_cmd "WinMergeU.exe" "WinMerge"
}
+
+merge_cmd_help () {
+ echo "Use WinMerge (requires a graphical session)"
+}
diff --git a/mergetools/xxdiff b/mergetools/xxdiff
index d5ce467..cd205f9 100644
--- a/mergetools/xxdiff
+++ b/mergetools/xxdiff
@@ -12,6 +12,10 @@
fi
}
+diff_cmd_help () {
+ echo "Use xxdiff (requires a graphical session)"
+}
+
merge_cmd () {
if $base_present
then
@@ -28,3 +32,7 @@
--merged-file "$MERGED" "$LOCAL" "$REMOTE"
fi
}
+
+merge_cmd_help () {
+ echo "Use xxdiff (requires a graphical session)"
+}
diff --git a/midx.c b/midx.c
index 107365d..3db0e47 100644
--- a/midx.c
+++ b/midx.c
@@ -1132,17 +1132,26 @@ static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash,
static struct multi_pack_index *lookup_multi_pack_index(struct repository *r,
const char *object_dir)
{
+ struct multi_pack_index *result = NULL;
struct multi_pack_index *cur;
+ char *obj_dir_real = real_pathdup(object_dir, 1);
+ struct strbuf cur_path_real = STRBUF_INIT;
/* Ensure the given object_dir is local, or a known alternate. */
- find_odb(r, object_dir);
+ find_odb(r, obj_dir_real);
for (cur = get_multi_pack_index(r); cur; cur = cur->next) {
- if (!strcmp(object_dir, cur->object_dir))
- return cur;
+ strbuf_realpath(&cur_path_real, cur->object_dir, 1);
+ if (!strcmp(obj_dir_real, cur_path_real.buf)) {
+ result = cur;
+ goto cleanup;
+ }
}
- return NULL;
+cleanup:
+ free(obj_dir_real);
+ strbuf_release(&cur_path_real);
+ return result;
}
static int write_midx_internal(const char *object_dir,
diff --git a/object-store.h b/object-store.h
index bd2322e..5399601 100644
--- a/object-store.h
+++ b/object-store.h
@@ -312,10 +312,6 @@ int has_object(struct repository *r, const struct object_id *oid,
* These functions can be removed once all callers have migrated to
* has_object() and/or oid_object_info_extended().
*/
-#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
-#define has_sha1_file_with_flags(sha1, flags) repo_has_sha1_file_with_flags(the_repository, sha1, flags)
-#define has_sha1_file(sha1) repo_has_sha1_file(the_repository, sha1)
-#endif
int repo_has_object_file(struct repository *r, const struct object_id *oid);
int repo_has_object_file_with_flags(struct repository *r,
const struct object_id *oid, int flags);
diff --git a/refs.c b/refs.c
index 9db66e9..90bcb27 100644
--- a/refs.c
+++ b/refs.c
@@ -1109,8 +1109,10 @@ int ref_transaction_create(struct ref_transaction *transaction,
unsigned int flags, const char *msg,
struct strbuf *err)
{
- if (!new_oid || is_null_oid(new_oid))
- BUG("create called without valid new_oid");
+ if (!new_oid || is_null_oid(new_oid)) {
+ strbuf_addf(err, "'%s' has a null OID", refname);
+ return 1;
+ }
return ref_transaction_update(transaction, refname, new_oid,
null_oid(), flags, msg, err);
}
diff --git a/reftable/blocksource.c b/reftable/blocksource.c
index 2605371..8331b34 100644
--- a/reftable/blocksource.c
+++ b/reftable/blocksource.c
@@ -15,7 +15,8 @@ license that can be found in the LICENSE file or at
static void strbuf_return_block(void *b, struct reftable_block *dest)
{
- memset(dest->data, 0xff, dest->len);
+ if (dest->len)
+ memset(dest->data, 0xff, dest->len);
reftable_free(dest->data);
}
@@ -56,7 +57,8 @@ void block_source_from_strbuf(struct reftable_block_source *bs,
static void malloc_return_block(void *b, struct reftable_block *dest)
{
- memset(dest->data, 0xff, dest->len);
+ if (dest->len)
+ memset(dest->data, 0xff, dest->len);
reftable_free(dest->data);
}
@@ -85,7 +87,8 @@ static uint64_t file_size(void *b)
static void file_return_block(void *b, struct reftable_block *dest)
{
- memset(dest->data, 0xff, dest->len);
+ if (dest->len)
+ memset(dest->data, 0xff, dest->len);
reftable_free(dest->data);
}
diff --git a/revision.c b/revision.c
index 7d435f8..c367273 100644
--- a/revision.c
+++ b/revision.c
@@ -1440,6 +1440,9 @@ static int limit_list(struct rev_info *revs)
if (revs->min_age != -1 && (commit->date > revs->min_age) &&
!revs->line_level_traverse)
continue;
+ if (revs->max_age_as_filter != -1 &&
+ (commit->date < revs->max_age_as_filter) && !revs->line_level_traverse)
+ continue;
date = commit->date;
p = &commit_list_insert(commit, p)->next;
@@ -1838,6 +1841,7 @@ void repo_init_revisions(struct repository *r,
revs->dense = 1;
revs->prefix = prefix;
revs->max_age = -1;
+ revs->max_age_as_filter = -1;
revs->min_age = -1;
revs->skip_count = -1;
revs->max_count = -1;
@@ -2218,6 +2222,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if ((argcount = parse_long_opt("since", argv, &optarg))) {
revs->max_age = approxidate(optarg);
return argcount;
+ } else if ((argcount = parse_long_opt("since-as-filter", argv, &optarg))) {
+ revs->max_age_as_filter = approxidate(optarg);
+ return argcount;
} else if ((argcount = parse_long_opt("after", argv, &optarg))) {
revs->max_age = approxidate(optarg);
return argcount;
@@ -3862,6 +3869,9 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
if (revs->min_age != -1 &&
comparison_date(revs, commit) > revs->min_age)
return commit_ignore;
+ if (revs->max_age_as_filter != -1 &&
+ comparison_date(revs, commit) < revs->max_age_as_filter)
+ return commit_ignore;
if (revs->min_parents || (revs->max_parents >= 0)) {
int n = commit_list_count(commit->parents);
if ((n < revs->min_parents) ||
diff --git a/revision.h b/revision.h
index 5bc59c7..e80c148 100644
--- a/revision.h
+++ b/revision.h
@@ -263,6 +263,7 @@ struct rev_info {
int skip_count;
int max_count;
timestamp_t max_age;
+ timestamp_t max_age_as_filter;
timestamp_t min_age;
int min_parents;
int max_parents;
diff --git a/run-command.h b/run-command.h
index 07bed6c..5bd0c93 100644
--- a/run-command.h
+++ b/run-command.h
@@ -142,7 +142,6 @@ struct child_process {
unsigned clean_on_exit:1;
unsigned wait_after_clean:1;
void (*clean_on_exit_handler)(struct child_process *process);
- void *clean_on_exit_handler_cbdata;
};
#define CHILD_PROCESS_INIT { \
diff --git a/sequencer.c b/sequencer.c
index a5f678f..8c3ed35 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1327,7 +1327,6 @@ void print_commit_summary(struct repository *r,
get_commit_format(format.buf, &rev);
rev.always_show_header = 0;
rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
- rev.diffopt.break_opt = 0;
diff_setup_done(&rev.diffopt);
refs = get_main_ref_store(the_repository);
diff --git a/serve.c b/serve.c
index b3fe9b5..733347f 100644
--- a/serve.c
+++ b/serve.c
@@ -3,7 +3,6 @@
#include "config.h"
#include "pkt-line.h"
#include "version.h"
-#include "strvec.h"
#include "ls-refs.h"
#include "protocol-caps.h"
#include "serve.h"
diff --git a/submodule-config.h b/submodule-config.h
index fa229a8..28a8ca6 100644
--- a/submodule-config.h
+++ b/submodule-config.h
@@ -38,7 +38,7 @@ struct submodule {
const char *path;
const char *name;
const char *url;
- int fetch_recurse;
+ enum submodule_recurse_mode fetch_recurse;
const char *ignore;
const char *branch;
struct submodule_update_strategy update_strategy;
diff --git a/submodule.h b/submodule.h
index 40c1445..437bc96 100644
--- a/submodule.h
+++ b/submodule.h
@@ -13,7 +13,7 @@ struct repository;
struct string_list;
struct strbuf;
-enum {
+enum submodule_recurse_mode {
RECURSE_SUBMODULES_ONLY = -5,
RECURSE_SUBMODULES_CHECK = -4,
RECURSE_SUBMODULES_ERROR = -3,
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index 09e86f9..cc01d89 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -56,6 +56,10 @@
' "$@" <actual
}
+get_progress_result () {
+ tr '\015' '\012' | tail -n 1
+}
+
test_expect_success 'setup A lines' '
echo "1A quick brown fox jumps over the" >file &&
echo "lazy dog" >>file &&
@@ -604,3 +608,39 @@
test_expect_success 'blame -L ,^/RE/' '
test_must_fail $PROG -L1,^/99/ file
'
+
+test_expect_success 'blame progress on a full file' '
+ cat >expect <<-\EOF &&
+ Blaming lines: 100% (10/10), done.
+ EOF
+
+ GIT_PROGRESS_DELAY=0 \
+ git blame --progress hello.c 2>stderr &&
+
+ get_progress_result <stderr >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'blame progress on a single range' '
+ cat >expect <<-\EOF &&
+ Blaming lines: 100% (4/4), done.
+ EOF
+
+ GIT_PROGRESS_DELAY=0 \
+ git blame --progress -L 3,6 hello.c 2>stderr &&
+
+ get_progress_result <stderr >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'blame progress on multiple ranges' '
+ cat >expect <<-\EOF &&
+ Blaming lines: 100% (7/7), done.
+ EOF
+
+ GIT_PROGRESS_DELAY=0 \
+ git blame --progress -L 3,6 -L 8,10 hello.c 2>stderr &&
+
+ get_progress_result <stderr >actual &&
+ test_cmp expect actual
+'
diff --git a/t/t0012-help.sh b/t/t0012-help.sh
index 6c3e1f7..6c33a43 100755
--- a/t/t0012-help.sh
+++ b/t/t0012-help.sh
@@ -181,7 +181,7 @@
do
test_expect_success "'$cmd' section spacing" '
test_section_spacing_trailer git help <<-\EOF &&
- usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
+ usage: git [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]
These are common Git commands used in various situations:
diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh
index 0feb41a..7f80f46 100755
--- a/t/t0027-auto-crlf.sh
+++ b/t/t0027-auto-crlf.sh
@@ -77,12 +77,12 @@
check_warning () {
case "$1" in
- LF_CRLF) echo "warning: LF will be replaced by CRLF" >"$2".expect ;;
- CRLF_LF) echo "warning: CRLF will be replaced by LF" >"$2".expect ;;
- '') >"$2".expect ;;
+ LF_CRLF) echo "LF will be replaced by CRLF" >"$2".expect ;;
+ CRLF_LF) echo "CRLF will be replaced by LF" >"$2".expect ;;
+ '') >"$2".expect ;;
*) echo >&2 "Illegal 1": "$1" ; return false ;;
esac
- grep "will be replaced by" "$2" | sed -e "s/\(.*\) in [^ ]*$/\1/" | uniq >"$2".actual
+ sed -e "s/^.* \([^ ]* will be replaced by [^ ]*\) .*$/\1/" "$2" | uniq >"$2".actual
test_cmp "$2".expect "$2".actual
}
diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh
index 239d93f..238b25f 100755
--- a/t/t0033-safe-directory.sh
+++ b/t/t0033-safe-directory.sh
@@ -9,13 +9,41 @@
expect_rejected_dir () {
test_must_fail git status 2>err &&
- grep "safe.directory" err
+ grep "unsafe repository" err
}
test_expect_success 'safe.directory is not set' '
expect_rejected_dir
'
+test_expect_success 'ignoring safe.directory on the command line' '
+ test_must_fail git -c safe.directory="$(pwd)" status 2>err &&
+ grep "unsafe repository" err
+'
+
+test_expect_success 'ignoring safe.directory in the environment' '
+ test_must_fail env GIT_CONFIG_COUNT=1 \
+ GIT_CONFIG_KEY_0="safe.directory" \
+ GIT_CONFIG_VALUE_0="$(pwd)" \
+ git status 2>err &&
+ grep "unsafe repository" err
+'
+
+test_expect_success 'ignoring safe.directory in GIT_CONFIG_PARAMETERS' '
+ test_must_fail env \
+ GIT_CONFIG_PARAMETERS="${SQ}safe.directory${SQ}=${SQ}$(pwd)${SQ}" \
+ git status 2>err &&
+ grep "unsafe repository" err
+'
+
+test_expect_success 'ignoring safe.directory in repo config' '
+ (
+ unset GIT_TEST_ASSUME_DIFFERENT_OWNER &&
+ git config safe.directory "$(pwd)"
+ ) &&
+ expect_rejected_dir
+'
+
test_expect_success 'safe.directory does not match' '
git config --global safe.directory bogus &&
expect_rejected_dir
diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
index dd957be..63a553d 100755
--- a/t/t1011-read-tree-sparse-checkout.sh
+++ b/t/t1011-read-tree-sparse-checkout.sh
@@ -57,8 +57,8 @@
read_tree_u_must_succeed -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt result &&
- test -f init.t &&
- test -f sub/added
+ test_path_is_file init.t &&
+ test_path_is_file sub/added
'
test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' '
@@ -67,8 +67,8 @@
read_tree_u_must_succeed --no-sparse-checkout -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt result &&
- test -f init.t &&
- test -f sub/added
+ test_path_is_file init.t &&
+ test_path_is_file sub/added
'
test_expect_success 'read-tree with empty .git/info/sparse-checkout' '
@@ -85,8 +85,8 @@
S subsub/added
EOF
test_cmp expected.swt result &&
- ! test -f init.t &&
- ! test -f sub/added
+ test_path_is_missing init.t &&
+ test_path_is_missing sub/added
'
test_expect_success 'match directories with trailing slash' '
@@ -101,8 +101,8 @@
read_tree_u_must_succeed -m -u HEAD &&
git ls-files -t > result &&
test_cmp expected.swt-noinit result &&
- test ! -f init.t &&
- test -f sub/added
+ test_path_is_missing init.t &&
+ test_path_is_file sub/added
'
test_expect_success 'match directories without trailing slash' '
@@ -110,8 +110,8 @@
read_tree_u_must_succeed -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt-noinit result &&
- test ! -f init.t &&
- test -f sub/added
+ test_path_is_missing init.t &&
+ test_path_is_file sub/added
'
test_expect_success 'match directories with negated patterns' '
@@ -129,9 +129,9 @@
git read-tree -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt-negation result &&
- test ! -f init.t &&
- test ! -f sub/added &&
- test -f sub/addedtoo
+ test_path_is_missing init.t &&
+ test_path_is_missing sub/added &&
+ test_path_is_file sub/addedtoo
'
test_expect_success 'match directories with negated patterns (2)' '
@@ -150,9 +150,9 @@
git read-tree -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt-negation2 result &&
- test -f init.t &&
- test -f sub/added &&
- test ! -f sub/addedtoo
+ test_path_is_file init.t &&
+ test_path_is_file sub/added &&
+ test_path_is_missing sub/addedtoo
'
test_expect_success 'match directory pattern' '
@@ -160,8 +160,8 @@
read_tree_u_must_succeed -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt-noinit result &&
- test ! -f init.t &&
- test -f sub/added
+ test_path_is_missing init.t &&
+ test_path_is_file sub/added
'
test_expect_success 'checkout area changes' '
@@ -176,15 +176,15 @@
read_tree_u_must_succeed -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt-nosub result &&
- test -f init.t &&
- test ! -f sub/added
+ test_path_is_file init.t &&
+ test_path_is_missing sub/added
'
test_expect_success 'read-tree updates worktree, absent case' '
echo sub/added >.git/info/sparse-checkout &&
git checkout -f top &&
read_tree_u_must_succeed -m -u HEAD^ &&
- test ! -f init.t
+ test_path_is_missing init.t
'
test_expect_success 'read-tree will not throw away dirty changes, non-sparse' '
@@ -229,7 +229,7 @@
echo init.t >.git/info/sparse-checkout &&
git checkout -f removed &&
read_tree_u_must_succeed -u -m HEAD^ &&
- test ! -f sub/added
+ test_path_is_missing sub/added
'
test_expect_success 'read-tree adds to worktree, dirty case' '
@@ -248,7 +248,7 @@
echo init.t >.git/info/sparse-checkout &&
git checkout removed &&
git ls-files sub/added >result &&
- test ! -f sub/added &&
+ test_path_is_missing sub/added &&
test_must_be_empty result
'
diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh
index 3716a42..3e04802 100755
--- a/t/t3416-rebase-onto-threedots.sh
+++ b/t/t3416-rebase-onto-threedots.sh
@@ -129,6 +129,20 @@
test_cmp expect actual
'
+test_expect_success 'rebase --keep-base main topic from main' '
+ git checkout main &&
+ git branch -f topic G &&
+
+ git rebase --keep-base main topic &&
+ git rev-parse C >base.expect &&
+ git merge-base main HEAD >base.actual &&
+ test_cmp base.expect base.actual &&
+
+ git rev-parse HEAD~2 >actual &&
+ git rev-parse C^0 >expect &&
+ test_cmp expect actual
+'
+
test_expect_success 'rebase --keep-base main from side' '
git reset --hard &&
git checkout side &&
@@ -153,6 +167,21 @@
test_cmp expect actual
'
+test_expect_success 'rebase -i --keep-base main topic from main' '
+ git checkout main &&
+ git branch -f topic G &&
+
+ set_fake_editor &&
+ EXPECT_COUNT=2 git rebase -i --keep-base main topic &&
+ git rev-parse C >base.expect &&
+ git merge-base main HEAD >base.actual &&
+ test_cmp base.expect base.actual &&
+
+ git rev-parse HEAD~2 >actual &&
+ git rev-parse C^0 >expect &&
+ test_cmp expect actual
+'
+
test_expect_success 'rebase -i --keep-base main from side' '
git reset --hard &&
git checkout side &&
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index 8617efa..9eb1920 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh
@@ -66,8 +66,7 @@
git checkout rename2 &&
git cherry-pick added &&
- test $(git rev-parse HEAD^) = $(git rev-parse rename2) &&
- test -f opos &&
+ test_cmp_rev rename2 HEAD^ &&
grep "Add extra line at the end" opos &&
git reflog -1 | grep cherry-pick
@@ -77,9 +76,9 @@
git checkout rename1 &&
git revert added &&
- test $(git rev-parse HEAD^) = $(git rev-parse rename1) &&
- test -f spoo &&
- ! grep "Add extra line at the end" spoo &&
+ test_cmp_rev rename1 HEAD^ &&
+ test_path_is_file spoo &&
+ test_cmp_rev initial:oops HEAD:spoo &&
git reflog -1 | grep revert
'
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 750aee1..056e922 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -542,6 +542,39 @@
test_cmp expect actual
'
+test_expect_success 'diff-tree --stdin with pathspec' '
+ cat >expect <<-EOF &&
+ Third
+
+ dir/sub
+ Second
+
+ dir/sub
+ EOF
+ git rev-list master^ |
+ git diff-tree -r --stdin --name-only --format=%s dir >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'show A B ... -- <pathspec>' '
+ # side touches dir/sub, file0, and file3
+ # master^ touches dir/sub, and file1
+ # master^^ touches dir/sub, file0, and file2
+ git show --name-only --format="<%s>" side master^ master^^ -- dir >actual &&
+ cat >expect <<-\EOF &&
+ <Side>
+
+ dir/sub
+ <Third>
+
+ dir/sub
+ <Second>
+
+ dir/sub
+ EOF
+ test_cmp expect actual
+'
+
test_expect_success 'diff -I<regex>: setup' '
git checkout master &&
test_seq 50 >file0 &&
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 7dc5a5c..fbec8ad 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -926,11 +926,40 @@
'
test_expect_success 'format-patch -- <path>' '
- git format-patch main..side -- file 2>error &&
- ! grep "Use .--" error
+ rm -f *.patch &&
+ git checkout -b pathspec main &&
+
+ echo file_a 1 >file_a &&
+ echo file_b 1 >file_b &&
+ git add file_a file_b &&
+ git commit -m pathspec_initial &&
+
+ echo file_a 2 >>file_a &&
+ git add file_a &&
+ git commit -m pathspec_a &&
+
+ echo file_b 2 >>file_b &&
+ git add file_b &&
+ git commit -m pathspec_b &&
+
+ echo file_a 3 >>file_a &&
+ echo file_b 3 >>file_b &&
+ git add file_a file_b &&
+ git commit -m pathspec_ab &&
+
+ cat >expect <<-\EOF &&
+ 0001-pathspec_initial.patch
+ 0002-pathspec_a.patch
+ 0003-pathspec_ab.patch
+ EOF
+
+ git format-patch main..pathspec -- file_a >output &&
+ test_cmp expect output &&
+ ! grep file_b *.patch
'
test_expect_success 'format-patch --ignore-if-in-upstream HEAD' '
+ git checkout side &&
git format-patch --ignore-if-in-upstream HEAD
'
diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh
index 1219f8b..858a552 100755
--- a/t/t4020-diff-external.sh
+++ b/t/t4020-diff-external.sh
@@ -206,17 +206,17 @@
'
test_expect_success 'GIT_EXTERNAL_DIFF generates pretty paths' '
+ test_when_finished "git rm -f file.ext" &&
touch file.ext &&
git add file.ext &&
echo with extension > file.ext &&
cat >expect <<-EOF &&
- file.ext file $(git rev-parse --verify HEAD:file) 100644 file.ext $(test_oid zero) 100644
+ file.ext
EOF
GIT_EXTERNAL_DIFF=echo git diff file.ext >out &&
- cut -d" " -f1,3- <out >actual &&
- git update-index --force-remove file.ext &&
- rm file.ext
+ basename $(cut -d" " -f2 <out) >actual &&
+ test_cmp expect actual
'
echo "#!$SHELL_PATH" >fake-diff.sh
diff --git a/t/t4217-log-limit.sh b/t/t4217-log-limit.sh
new file mode 100755
index 0000000..6e01e26
--- /dev/null
+++ b/t/t4217-log-limit.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+test_description='git log with filter options limiting the output'
+
+. ./test-lib.sh
+
+test_expect_success 'setup test' '
+ git init &&
+ echo a >file &&
+ git add file &&
+ GIT_COMMITTER_DATE="2021-02-01 00:00" git commit -m init &&
+ echo a >>file &&
+ git add file &&
+ GIT_COMMITTER_DATE="2022-02-01 00:00" git commit -m first &&
+ echo a >>file &&
+ git add file &&
+ GIT_COMMITTER_DATE="2021-03-01 00:00" git commit -m second &&
+ echo a >>file &&
+ git add file &&
+ GIT_COMMITTER_DATE="2022-03-01 00:00" git commit -m third
+'
+
+test_expect_success 'git log --since-as-filter=...' '
+ git log --since-as-filter="2022-01-01" --format=%s >actual &&
+ cat >expect <<-\EOF &&
+ third
+ first
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'git log --children --since-as-filter=...' '
+ git log --children --since-as-filter="2022-01-01" --format=%s >actual &&
+ cat >expect <<-\EOF &&
+ third
+ first
+ EOF
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh
index 7d63365..21ab619 100755
--- a/t/t5605-clone-local.sh
+++ b/t/t5605-clone-local.sh
@@ -141,4 +141,13 @@
test_must_fail git clone --bare -u false a should_not_work.git
'
+test_expect_success 'local clone from repo with corrupt refs fails gracefully' '
+ git init corrupt &&
+ test_commit -C corrupt one &&
+ echo a >corrupt/.git/refs/heads/topic &&
+
+ test_must_fail git clone corrupt working 2>err &&
+ grep "has a null OID" err
+'
+
test_done
diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh
index ca90ee8..9936cc3 100755
--- a/t/t7063-status-untracked-cache.sh
+++ b/t/t7063-status-untracked-cache.sh
@@ -190,6 +190,119 @@
test_cmp ../dump.expect ../actual
'
+cat >../status_uall.expect <<EOF &&
+A done/one
+A one
+A two
+?? dthree/three
+?? dtwo/two
+?? three
+EOF
+
+# Bypassing the untracked cache here is not desirable from an
+# end-user perspective, but is expected in the current design.
+# The untracked cache data stored for a -unormal run cannot be
+# correctly used in a -uall run - it would yield incorrect output.
+test_expect_success 'untracked cache is bypassed with -uall' '
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
+ git status -uall --porcelain >../actual &&
+ iuc status -uall --porcelain >../status.iuc &&
+ test_cmp ../status_uall.expect ../status.iuc &&
+ test_cmp ../status_uall.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
+ cat >../trace.expect <<EOF &&
+ ....path:
+EOF
+ test_cmp ../trace.expect ../trace.relevant
+'
+
+test_expect_success 'untracked cache remains after bypass' '
+ test-tool dump-untracked-cache >../actual &&
+ test_cmp ../dump.expect ../actual
+'
+
+test_expect_success 'if -uall is configured, untracked cache gets populated by default' '
+ test_config status.showuntrackedfiles all &&
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
+ git status --porcelain >../actual &&
+ iuc status --porcelain >../status.iuc &&
+ test_cmp ../status_uall.expect ../status.iuc &&
+ test_cmp ../status_uall.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
+ cat >../trace.expect <<EOF &&
+ ....path:
+ ....node-creation:3
+ ....gitignore-invalidation:1
+ ....directory-invalidation:0
+ ....opendir:4
+EOF
+ test_cmp ../trace.expect ../trace.relevant
+'
+
+cat >../dump_uall.expect <<EOF &&
+info/exclude $EMPTY_BLOB
+core.excludesfile $ZERO_OID
+exclude_per_dir .gitignore
+flags 00000000
+/ $ZERO_OID recurse valid
+three
+/done/ $ZERO_OID recurse valid
+/dthree/ $ZERO_OID recurse valid
+three
+/dtwo/ $ZERO_OID recurse valid
+two
+EOF
+
+test_expect_success 'if -uall was configured, untracked cache is populated' '
+ test-tool dump-untracked-cache >../actual &&
+ test_cmp ../dump_uall.expect ../actual
+'
+
+test_expect_success 'if -uall is configured, untracked cache is used by default' '
+ test_config status.showuntrackedfiles all &&
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
+ git status --porcelain >../actual &&
+ iuc status --porcelain >../status.iuc &&
+ test_cmp ../status_uall.expect ../status.iuc &&
+ test_cmp ../status_uall.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
+ cat >../trace.expect <<EOF &&
+ ....path:
+ ....node-creation:0
+ ....gitignore-invalidation:0
+ ....directory-invalidation:0
+ ....opendir:0
+EOF
+ test_cmp ../trace.expect ../trace.relevant
+'
+
+# Bypassing the untracked cache here is not desirable from an
+# end-user perspective, but is expected in the current design.
+# The untracked cache data stored for a -all run cannot be
+# correctly used in a -unormal run - it would yield incorrect
+# output.
+test_expect_success 'if -uall is configured, untracked cache is bypassed with -unormal' '
+ test_config status.showuntrackedfiles all &&
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
+ git status -unormal --porcelain >../actual &&
+ iuc status -unormal --porcelain >../status.iuc &&
+ test_cmp ../status.expect ../status.iuc &&
+ test_cmp ../status.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
+ cat >../trace.expect <<EOF &&
+ ....path:
+EOF
+ test_cmp ../trace.expect ../trace.relevant
+'
+
+test_expect_success 'repopulate untracked cache for -unormal' '
+ git status --porcelain
+'
+
test_expect_success 'modify in root directory, one dir invalidation' '
: >four &&
test-tool chmtime =-240 four &&
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index 000e055..43f779d 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -670,6 +670,39 @@
)
'
+test_expect_success 'submodule update with pathspec warns against uninitialized ones' '
+ test_when_finished "rm -fr selective" &&
+ git clone super selective &&
+ (
+ cd selective &&
+ git submodule init submodule &&
+
+ git submodule update submodule 2>err &&
+ ! grep "Submodule path .* not initialized" err &&
+
+ git submodule update rebasing 2>err &&
+ grep "Submodule path .rebasing. not initialized" err &&
+
+ test_path_exists submodule/.git &&
+ test_path_is_missing rebasing/.git
+ )
+
+'
+
+test_expect_success 'submodule update without pathspec updates only initialized ones' '
+ test_when_finished "rm -fr selective" &&
+ git clone super selective &&
+ (
+ cd selective &&
+ git submodule init submodule &&
+ git submodule update 2>err &&
+ test_path_exists submodule/.git &&
+ test_path_is_missing rebasing/.git &&
+ ! grep "Submodule path .* not initialized" err
+ )
+
+'
+
test_expect_success 'submodule update continues after checkout error' '
(cd super &&
git reset --hard HEAD &&
diff --git a/t/t7524-commit-summary.sh b/t/t7524-commit-summary.sh
new file mode 100755
index 0000000..47b2f1d
--- /dev/null
+++ b/t/t7524-commit-summary.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+test_description='git commit summary'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_seq 101 200 >file &&
+ git add file &&
+ git commit -m initial &&
+ git tag initial
+'
+
+test_expect_success 'commit summary ignores rewrites' '
+ git reset --hard initial &&
+ test_seq 200 300 >file &&
+
+ git diff --stat >diffstat &&
+ git diff --stat --break-rewrites >diffstatrewrite &&
+
+ # make sure this scenario is a detectable rewrite
+ ! test_cmp_bin diffstat diffstatrewrite &&
+
+ git add file &&
+ git commit -m second >actual &&
+
+ grep "1 file" <actual >actual.total &&
+ grep "1 file" <diffstat >diffstat.total &&
+ test_cmp diffstat.total actual.total
+'
+
+test_done
diff --git a/t/t7609-mergetool--lib.sh b/t/t7609-mergetool--lib.sh
new file mode 100755
index 0000000..d848fe6
--- /dev/null
+++ b/t/t7609-mergetool--lib.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+test_description='git mergetool
+
+Testing basic merge tools options'
+
+. ./test-lib.sh
+
+test_expect_success 'mergetool --tool=vimdiff creates the expected layout' '
+ . $GIT_BUILD_DIR/mergetools/vimdiff &&
+ run_unit_tests
+'
+
+test_done
diff --git a/t/t7812-grep-icase-non-ascii.sh b/t/t7812-grep-icase-non-ascii.sh
index 9047d66..ac7be54 100755
--- a/t/t7812-grep-icase-non-ascii.sh
+++ b/t/t7812-grep-icase-non-ascii.sh
@@ -4,6 +4,10 @@
. ./lib-gettext.sh
+doalarm () {
+ perl -e 'alarm shift; exec @ARGV' -- "$@"
+}
+
test_expect_success GETTEXT_LOCALE 'setup' '
test_write_lines "TILRAUN: Halló Heimur!" >file &&
git add file &&
@@ -139,4 +143,10 @@
test_cmp expected actual
'
+test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep avoid endless loop bug' '
+ echo " Halló" >leading-whitespace &&
+ git add leading-whitespace &&
+ doalarm 1 git grep --perl-regexp "^\s" leading-whitespace
+'
+
test_done
diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
index 7b7a18d..fc99703 100755
--- a/t/t9350-fast-export.sh
+++ b/t/t9350-fast-export.sh
@@ -500,6 +500,13 @@
grep file0 actual
'
+test_expect_success 'path limiting works' '
+ git fast-export simple -- file >actual &&
+ sed -ne "s/^M .* //p" <actual | sort -u >actual.files &&
+ echo file >expect &&
+ test_cmp expect actual.files
+'
+
test_expect_success 'avoid corrupt stream with non-existent mark' '
test_create_repo avoid_non_existent_mark &&
(
diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh
index 8b30062..dc88d0e 100755
--- a/t/t9800-git-p4-basic.sh
+++ b/t/t9800-git-p4-basic.sh
@@ -74,6 +74,91 @@
)
'
+#
+# Setup as before, and then explicitly sync imported branch, using a
+# different ref format.
+#
+test_expect_success 'git p4 sync existing branch without changes' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=depot //depot@all &&
+ git p4 sync --branch=refs/remotes/p4/depot >out &&
+ test_i18ngrep "No changes to import!" out
+ )
+'
+
+#
+# Same as before, relative branch name.
+#
+test_expect_success 'git p4 sync existing branch with relative name' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=branch1 //depot@all &&
+ git p4 sync --branch=p4/branch1 >out &&
+ test_i18ngrep "No changes to import!" out
+ )
+'
+
+#
+# Same as before, with a nested branch path, referenced different ways.
+#
+test_expect_success 'git p4 sync existing branch with nested path' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=p4/some/path //depot@all &&
+ git p4 sync --branch=some/path >out &&
+ test_i18ngrep "No changes to import!" out
+ )
+'
+
+#
+# Same as before, with a full ref path outside the p4/* namespace.
+#
+test_expect_success 'git p4 sync branch explicit ref without p4 in path' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=refs/remotes/someremote/depot //depot@all &&
+ git p4 sync --branch=refs/remotes/someremote/depot >out &&
+ test_i18ngrep "No changes to import!" out
+ )
+'
+
+test_expect_success 'git p4 sync nonexistent ref' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=depot //depot@all &&
+ test_must_fail git p4 sync --branch=depot2 2>errs &&
+ test_i18ngrep "Perhaps you never did" errs
+ )
+'
+
+test_expect_success 'git p4 sync existing non-p4-imported ref' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=depot //depot@all &&
+ test_must_fail git p4 sync --branch=refs/heads/master 2>errs &&
+ test_i18ngrep "Perhaps you never did" errs
+ )
+'
+
test_expect_success 'clone two dirs' '
(
cd "$cli" &&
diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh
index 50a6f8b..759a14f 100755
--- a/t/t9801-git-p4-branch.sh
+++ b/t/t9801-git-p4-branch.sh
@@ -129,6 +129,16 @@
)
'
+test_expect_success 'sync specific detected branch' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" --detect-branches //depot@all &&
+ (
+ cd "$git" &&
+ git p4 sync --branch=depot/branch2 >out &&
+ test_i18ngrep "No changes to import!" out
+ )
+'
+
test_expect_success 'import depot, branch detection, branchList branch definition' '
test_when_finished cleanup_git &&
test_create_repo "$git" &&
diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh
index 19073c6..2a6ee2a 100755
--- a/t/t9802-git-p4-filetype.sh
+++ b/t/t9802-git-p4-filetype.sh
@@ -333,4 +333,38 @@
)
'
+test_expect_success SYMLINKS 'utf-8 with and without BOM in text file' '
+ (
+ cd "$cli" &&
+
+ # some utf8 content
+ echo some tǣxt >utf8-nobom-test &&
+
+ # same utf8 content as before but with bom
+ echo some tǣxt | sed '\''s/^/\xef\xbb\xbf/'\'' >utf8-bom-test &&
+
+ # bom only
+ dd bs=1 count=3 if=utf8-bom-test of=utf8-bom-empty-test &&
+
+ p4 add utf8-nobom-test utf8-bom-test utf8-bom-empty-test &&
+ p4 submit -d "add utf8 test files"
+ ) &&
+ test_when_finished cleanup_git &&
+
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git checkout refs/remotes/p4/master &&
+
+ echo some tǣxt >utf8-nobom-check &&
+ test_cmp utf8-nobom-check utf8-nobom-test &&
+
+ echo some tǣxt | sed '\''s/^/\xef\xbb\xbf/'\'' >utf8-bom-check &&
+ test_cmp utf8-bom-check utf8-bom-test &&
+
+ dd bs=1 count=3 if=utf8-bom-check of=utf8-bom-empty-check &&
+ test_cmp utf8-bom-empty-check utf8-bom-empty-test
+ )
+'
+
test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 531cef0..f09e8f3 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -535,9 +535,10 @@
;;
esac
-# Add libc MALLOC and MALLOC_PERTURB test
-# only if we are not executing the test with valgrind
+# Add libc MALLOC and MALLOC_PERTURB test only if we are not executing
+# the test with valgrind and have not compiled with SANITIZE=address.
if test -n "$valgrind" ||
+ test -n "$SANITIZE_ADDRESS" ||
test -n "$TEST_NO_MALLOC_CHECK"
then
setup_malloc_check () {
diff --git a/tempfile.c b/tempfile.c
index 94aa18f..2024c82 100644
--- a/tempfile.c
+++ b/tempfile.c
@@ -56,6 +56,20 @@
static VOLATILE_LIST_HEAD(tempfile_list);
+static void remove_template_directory(struct tempfile *tempfile,
+ int in_signal_handler)
+{
+ if (tempfile->directorylen > 0 &&
+ tempfile->directorylen < tempfile->filename.len &&
+ tempfile->filename.buf[tempfile->directorylen] == '/') {
+ strbuf_setlen(&tempfile->filename, tempfile->directorylen);
+ if (in_signal_handler)
+ rmdir(tempfile->filename.buf);
+ else
+ rmdir_or_warn(tempfile->filename.buf);
+ }
+}
+
static void remove_tempfiles(int in_signal_handler)
{
pid_t me = getpid();
@@ -74,6 +88,7 @@ static void remove_tempfiles(int in_signal_handler)
unlink(p->filename.buf);
else
unlink_or_warn(p->filename.buf);
+ remove_template_directory(p, in_signal_handler);
p->active = 0;
}
@@ -100,6 +115,7 @@ static struct tempfile *new_tempfile(void)
tempfile->owner = 0;
INIT_LIST_HEAD(&tempfile->list);
strbuf_init(&tempfile->filename, 0);
+ tempfile->directorylen = 0;
return tempfile;
}
@@ -198,6 +214,52 @@ struct tempfile *mks_tempfile_tsm(const char *filename_template, int suffixlen,
return tempfile;
}
+struct tempfile *mks_tempfile_dt(const char *directory_template,
+ const char *filename)
+{
+ struct tempfile *tempfile;
+ const char *tmpdir;
+ struct strbuf sb = STRBUF_INIT;
+ int fd;
+ size_t directorylen;
+
+ if (!ends_with(directory_template, "XXXXXX")) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ tmpdir = getenv("TMPDIR");
+ if (!tmpdir)
+ tmpdir = "/tmp";
+
+ strbuf_addf(&sb, "%s/%s", tmpdir, directory_template);
+ directorylen = sb.len;
+ if (!mkdtemp(sb.buf)) {
+ int orig_errno = errno;
+ strbuf_release(&sb);
+ errno = orig_errno;
+ return NULL;
+ }
+
+ strbuf_addf(&sb, "/%s", filename);
+ fd = open(sb.buf, O_CREAT | O_EXCL | O_RDWR, 0600);
+ if (fd < 0) {
+ int orig_errno = errno;
+ strbuf_setlen(&sb, directorylen);
+ rmdir(sb.buf);
+ strbuf_release(&sb);
+ errno = orig_errno;
+ return NULL;
+ }
+
+ tempfile = new_tempfile();
+ strbuf_swap(&tempfile->filename, &sb);
+ tempfile->directorylen = directorylen;
+ tempfile->fd = fd;
+ activate_tempfile(tempfile);
+ return tempfile;
+}
+
struct tempfile *xmks_tempfile_m(const char *filename_template, int mode)
{
struct tempfile *tempfile;
@@ -316,6 +378,7 @@ void delete_tempfile(struct tempfile **tempfile_p)
close_tempfile_gently(tempfile);
unlink_or_warn(tempfile->filename.buf);
+ remove_template_directory(tempfile, 0);
deactivate_tempfile(tempfile);
*tempfile_p = NULL;
}
diff --git a/tempfile.h b/tempfile.h
index 4de3bc7..d7804a2 100644
--- a/tempfile.h
+++ b/tempfile.h
@@ -82,6 +82,7 @@ struct tempfile {
FILE *volatile fp;
volatile pid_t owner;
struct strbuf filename;
+ size_t directorylen;
};
/*
@@ -199,6 +200,18 @@ static inline struct tempfile *xmks_tempfile(const char *filename_template)
}
/*
+ * Attempt to create a temporary directory in $TMPDIR and to create and
+ * open a file in that new directory. Derive the directory name from the
+ * template in the manner of mkdtemp(). Arrange for directory and file
+ * to be deleted if the program exits before they are deleted
+ * explicitly. On success return a tempfile whose "filename" member
+ * contains the full path of the file and its "fd" member is open for
+ * writing the file. On error return NULL and set errno appropriately.
+ */
+struct tempfile *mks_tempfile_dt(const char *directory_template,
+ const char *filename);
+
+/*
* Associate a stdio stream with the temporary file (which must still
* be open). Return `NULL` (*without* deleting the file) on error. The
* stream is closed automatically when `close_tempfile_gently()` is called or
diff --git a/xdiff/xmacros.h b/xdiff/xmacros.h
index 2809a28..ae4636c 100644
--- a/xdiff/xmacros.h
+++ b/xdiff/xmacros.h
@@ -34,7 +34,6 @@
#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b)))
#define XDL_MASKBITS(b) ((1UL << (b)) - 1)
#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b))
-#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0)
#define XDL_LE32_PUT(p, v) \
do { \
unsigned char *__p = (unsigned char *) (p); \