* Re: Unmodified submodules shows up as dirty with 1.6.6.443.gd7346
From: Junio C Hamano @ 2010-01-18 17:22 UTC (permalink / raw)
To: Gustaf Hendeby; +Cc: Jacob Helwig, git, Jens.Lehmann
In-Reply-To: <4B549254.5090206@isy.liu.se>
Gustaf Hendeby <hendeby@isy.liu.se> writes:
> .... I don't want to include
> these files in a .gitignore as they are named differently on different
> systems. Hence, I include them in my .git/info/exclude file,...
I don't have a strong opinion on the submodules part of your issue, but
the above part applies to projects with or without submodules, which I
have an opinion, and because it is different from what I used to teach
people, I think it is worth mentioning..
I used to say "Never place *~ (or *.swp) in .gitignore because they are
only useful to you who use Emacs (or vim); and do have *.o in .gitignore,
because everybody who compile your checkout would see it".
But I don't think the former is a right attitude. My thinking these days
is that keeping these in .gitignore should not just be tolerated but
should be actively encouraged, unless the project may need to track paths
that match *~ or *.swp in the future,
If it is very unlikely that the project will ever track them, there is no
harm done [*1*], and it will help other people because they don't have to
add the same and common entries in their own .git/info/excludes file.
I am suspecting that your "these files ... are named differently on
different systems" may fall into the same category. Your build may not
produce "frotz.linux" when compiled on a FreeBSD box (and "frotz.fbsd" on
a Linux box), but would it hurt more than it helps to list them in the
same .gitignore to cover both?
[Footnote]
*1* Once it starts doing so, un-ignoring a special case can be done
at that point in the history
^ permalink raw reply
* Re: Unmodified submodules shows up as dirty with 1.6.6.443.gd7346
From: Jacob Helwig @ 2010-01-18 17:14 UTC (permalink / raw)
To: Gustaf Hendeby; +Cc: git, Jens.Lehmann
In-Reply-To: <4B549254.5090206@isy.liu.se>
On Mon, Jan 18, 2010 at 08:54, Gustaf Hendeby <hendeby@isy.liu.se> wrote:
> Jacob Helwig wrote:
>> On Mon, Jan 18, 2010 at 07:30, Gustaf Hendeby <hendeby@isy.liu.se> wrote:
>>> Hi!
>>>
>>> I have been using submodules for a while, and been quite happy with
>>> them. Just updating to the latest next (1.6.6.443.gd7346), a strange
>>> problem has occurred. All my submodules (which are in fact unmodified)
>>> show as modified and dirty
>>>
>>> diff --git a/extern/utils b/extern/utils
>>> --- a/extern/utils
>>> +++ b/extern/utils
>>> @@ -1 +1 @@
>>> -Subproject commit 6bad20e1419f1ca61bd5a6eef9b5937122e006f1
>>> +Subproject commit 6bad20e1419f1ca61bd5a6eef9b5937122e006f1-dirty
>>>
>>>
>> Do you have any untracked files in the submodule? git status is
>> working as I would expect with the same version (1.6.6.443.gd7346).
>
> Yes, I do.
>
>>
>> If there is no output from git status in the submodule, then git
>> status in the superproject shows the submodule as being clean.
>> However, if there is _any_ output from git status (untracked files,
>> modified files, deleted files, new files), then the superproject shows
>> the submodule as being dirty.
>>
>
> Then the behavior of this feature differs from the one provided by
> GIT-VERSION-GEN that is used as part of the git build process. This is
> not an argument itself, but personally, I don't like this behavior, and
> think it should be reconsidered before inclusion into master.
>
> I have the following use case, which is affected. I have with in a
> submodule some code that needs to be compiled, and hence generate some
> object files and other files in the process. I don't want to include
> these files in a .gitignore as they are named differently on different
> systems. Hence, I include them in my .git/info/exclude file, where I am
> developing the module. So now, unless I do the same thing for all
> places I checkout the repo as submodule, I end up with the module
> indicated as dirty after I compile it. This is a bit inconvenient.
>
> Am I the only one who uses submodules this way? Is there a better way
> to solve my problem that would provide a better work pattern in this case?
>
> /Gustaf
>
I don't really deal with compiled code, when I use submodules, so any
time there's a new file in one of my submodules, it's almost certainly
a new library file that should make the submodule be considered
"dirty". Even though the current behavior is what I'd expect, and
want for my uses, that doesn't mean it's not wrong for some other use
cases.
That being said:
The .gitignore file supports shell globs. Are the generated files
created with names that are so different that some simple shell globs
used in one or more .gitignore files couldn't cover them?
-Jacob
^ permalink raw reply
* [PATCH] Add git remote set-url
From: Ilari Liusvaara @ 2010-01-18 17:18 UTC (permalink / raw)
To: git
Add 'git remote set-url' for changing URL of remote repository with
one "porcelain-level" command.
Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
---
Built atop master. Another one from my TODO list.
Documentation/git-remote.txt | 17 ++++
builtin-remote.c | 100 +++++++++++++++++++
t/t5505-remote.sh | 216 +++++++++++++++++++++++++++++++++++++++++-
3 files changed, 332 insertions(+), 1 deletions(-)
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index c272c92..35d32f7 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -14,6 +14,9 @@ SYNOPSIS
'git remote rename' <old> <new>
'git remote rm' <name>
'git remote set-head' <name> (-a | -d | <branch>)
+'git remote set-url' [--push] <name> <newurl> [<oldurl>]
+'git remote set-url --add' [--push] <name> <newurl>
+'git remote set-url --delete' [--push] <name> <url>
'git remote' [-v | --verbose] 'show' [-n] <name>
'git remote prune' [-n | --dry-run] <name>
'git remote' [-v | --verbose] 'update' [-p | --prune] [group | remote]...
@@ -101,6 +104,20 @@ remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to
`refs/remotes/origin/master` already exists; if not it must be fetched first.
+
+'set-url'::
+
+Changes URL remote points to. Sets first URL remote points to matching
+regex <oldurl> (first URL if no <oldurl> is given) to <newurl>. If
+<oldurl> doesn't match any URL, error occurs and nothing is changed.
++
+With '--push', push URLs are manipulated instead of fetch URLs.
++
+With '--add', instead of changing some URL, new URL is added.
++
+With '--delete', instead of changing some URL, all URLs matching
+regex <url> are deleted. Trying to delete all non-push URLs is an
+error.
+
'show'::
Gives some information about the remote <name>.
diff --git a/builtin-remote.c b/builtin-remote.c
index c4945b8..03ac7ee 100644
--- a/builtin-remote.c
+++ b/builtin-remote.c
@@ -16,6 +16,9 @@ static const char * const builtin_remote_usage[] = {
"git remote [-v | --verbose] show [-n] <name>",
"git remote prune [-n | --dry-run] <name>",
"git remote [-v | --verbose] update [-p | --prune] [group | remote]",
+ "git remote set-url <name> <newurl> [<oldurl>]",
+ "git remote set-url --add <name> <newurl>",
+ "git remote set-url --delete <name> <url>",
NULL
};
@@ -54,12 +57,21 @@ static const char * const builtin_remote_update_usage[] = {
NULL
};
+static const char * const builtin_remote_seturl_usage[] = {
+ "git remote set-url [--push] <name> <newurl> [<oldurl>]",
+ "git remote set-url --add <name> <newurl>",
+ "git remote set-url --delete <name> <url>",
+ NULL
+};
+
#define GET_REF_STATES (1<<0)
#define GET_HEAD_NAMES (1<<1)
#define GET_PUSH_REF_STATES (1<<2)
static int verbose;
+
+
static int show_all(void);
static int prune_remote(const char *remote, int dry_run);
@@ -1255,6 +1267,92 @@ static int update(int argc, const char **argv)
return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
}
+static int set_url(int argc, const char **argv)
+{
+ int i, push_mode = 0, add_mode = 0, delete_mode = 0;
+ int matches = 0, negative_matches = 0;
+ const char *remotename = NULL;
+ const char *newurl = NULL;
+ const char *oldurl = NULL;
+ struct remote *remote;
+ regex_t old_regex;
+ const char **urlset;
+ int urlset_nr;
+ struct strbuf name_buf = STRBUF_INIT;
+ struct option options[] = {
+ OPT_BOOLEAN('\0', "push", &push_mode,
+ "manipulate push URLs"),
+ OPT_BOOLEAN('\0', "add", &add_mode,
+ "add URL"),
+ OPT_BOOLEAN('\0', "delete", &delete_mode,
+ "delete URLs"),
+ OPT_END()
+ };
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
+ PARSE_OPT_KEEP_ARGV0);
+
+ if (add_mode && delete_mode)
+ die("--add --delete doesn't make sense");
+
+ if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3))
+ usage_with_options(builtin_remote_seturl_usage, options);
+
+ remotename = argv[1];
+ newurl = argv[2];
+ if (argc > 3)
+ oldurl = argv[3];
+
+ if (delete_mode)
+ oldurl = newurl;
+
+ if (!remote_is_configured(remotename))
+ die("No such remote '%s'", remotename);
+ remote = remote_get(remotename);
+
+ if (push_mode) {
+ strbuf_addf(&name_buf, "remote.%s.pushurl", remotename);
+ urlset = remote->pushurl;
+ urlset_nr = remote->pushurl_nr;
+ } else {
+ strbuf_addf(&name_buf, "remote.%s.url", remotename);
+ urlset = remote->url;
+ urlset_nr = remote->url_nr;
+ }
+
+ /* Special cases that add new entry. */
+ if ((!oldurl && !delete_mode) || add_mode) {
+ if (add_mode)
+ git_config_set_multivar(name_buf.buf, newurl,
+ "^$", 0);
+ else
+ git_config_set(name_buf.buf, newurl);
+ strbuf_release(&name_buf);
+ return 0;
+ }
+
+ /* Old URL specified. Demand that one matches. */
+ if (regcomp(&old_regex, oldurl, REG_EXTENDED))
+ die("Invalid old URL pattern: %s", oldurl);
+
+ for (i = 0; i < urlset_nr; i++)
+ if (!regexec(&old_regex, urlset[i], 0, NULL, 0))
+ matches++;
+ else
+ negative_matches++;
+ if (!delete_mode && !matches)
+ die("No such URL found: %s", oldurl);
+ if (delete_mode && !negative_matches && !push_mode)
+ die("Will not delete all non-push URLs");
+
+ regfree(&old_regex);
+
+ if (!delete_mode)
+ git_config_set_multivar(name_buf.buf, newurl, oldurl, 0);
+ else
+ git_config_set_multivar(name_buf.buf, NULL, oldurl, 1);
+ return 0;
+}
+
static int get_one_entry(struct remote *remote, void *priv)
{
struct string_list *list = priv;
@@ -1334,6 +1432,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
result = rm(argc, argv);
else if (!strcmp(argv[0], "set-head"))
result = set_head(argc, argv);
+ else if (!strcmp(argv[0], "set-url"))
+ result = set_url(argc, argv);
else if (!strcmp(argv[0], "show"))
result = show(argc, argv);
else if (!strcmp(argv[0], "prune"))
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 936fe0a..a82c5ff 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -533,5 +533,219 @@ test_expect_success 'show empty remote' '
)
'
-test_done
+test_expect_success 'new remote' '
+(
+ git remote add someremote foo &&
+ echo foo >expect &&
+ git config --get-all remote.someremote.url >actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url bar' '
+(
+ git remote set-url someremote bar &&
+ echo bar >expect &&
+ git config --get-all remote.someremote.url >actual &&
+ cmp expect actual
+)
+'
+test_expect_success 'remote set-url baz bar' '
+(
+ git remote set-url someremote baz bar &&
+ echo baz >expect &&
+ git config --get-all remote.someremote.url >actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url zot bar' '
+(
+ test_must_fail git remote set-url someremote zot bar &&
+ echo baz >expect &&
+ git config --get-all remote.someremote.url >actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push zot baz' '
+(
+ test_must_fail git remote set-url --push someremote zot baz &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push zot' '
+(
+ git remote set-url --push someremote zot &&
+ echo zot >expect &&
+ echo "YYY" >>expect &&
+ echo baz >>expect &&
+ git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push qux zot' '
+(
+ git remote set-url --push someremote qux zot &&
+ echo qux >expect &&
+ echo "YYY" >>expect &&
+ echo baz >>expect &&
+ git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push foo qu+x' '
+(
+ git remote set-url --push someremote foo qu+x &&
+ echo foo >expect &&
+ echo "YYY" >>expect &&
+ echo baz >>expect &&
+ git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push --add aaa' '
+(
+ git remote set-url --push --add someremote aaa &&
+ echo foo >expect &&
+ echo aaa >>expect &&
+ echo "YYY" >>expect &&
+ echo baz >>expect &&
+ git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push bar aaa' '
+(
+ git remote set-url --push someremote bar aaa &&
+ echo foo >expect &&
+ echo bar >>expect &&
+ echo "YYY" >>expect &&
+ echo baz >>expect &&
+ git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push --delete bar' '
+(
+ git remote set-url --push --delete someremote bar &&
+ echo foo >expect &&
+ echo "YYY" >>expect &&
+ echo baz >>expect &&
+ git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push --delete foo' '
+(
+ git remote set-url --push --delete someremote foo &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --add bbb' '
+(
+ git remote set-url --add someremote bbb &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ echo bbb >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --delete .*' '
+(
+ test_must_fail git remote set-url --delete someremote .* &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ echo bbb >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --delete bbb' '
+(
+ git remote set-url --delete someremote bbb &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --delete baz' '
+(
+ test_must_fail git remote set-url --delete someremote baz &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --add ccc' '
+(
+ git remote set-url --add someremote ccc &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ echo ccc >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --delete baz' '
+(
+ git remote set-url --delete someremote baz &&
+ echo "YYY" >expect &&
+ echo ccc >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_done
--
1.6.6.199.gff4b0
^ permalink raw reply related
* Re: Unmodified submodules shows up as dirty with 1.6.6.443.gd7346
From: Gustaf Hendeby @ 2010-01-18 16:54 UTC (permalink / raw)
To: Jacob Helwig; +Cc: git, Jens.Lehmann
In-Reply-To: <8c9a061001180802t5ec0d389j2cae9f1771130c36@mail.gmail.com>
Jacob Helwig wrote:
> On Mon, Jan 18, 2010 at 07:30, Gustaf Hendeby <hendeby@isy.liu.se> wrote:
>> Hi!
>>
>> I have been using submodules for a while, and been quite happy with
>> them. Just updating to the latest next (1.6.6.443.gd7346), a strange
>> problem has occurred. All my submodules (which are in fact unmodified)
>> show as modified and dirty
>>
>> diff --git a/extern/utils b/extern/utils
>> --- a/extern/utils
>> +++ b/extern/utils
>> @@ -1 +1 @@
>> -Subproject commit 6bad20e1419f1ca61bd5a6eef9b5937122e006f1
>> +Subproject commit 6bad20e1419f1ca61bd5a6eef9b5937122e006f1-dirty
>>
>>
> Do you have any untracked files in the submodule? git status is
> working as I would expect with the same version (1.6.6.443.gd7346).
Yes, I do.
>
> If there is no output from git status in the submodule, then git
> status in the superproject shows the submodule as being clean.
> However, if there is _any_ output from git status (untracked files,
> modified files, deleted files, new files), then the superproject shows
> the submodule as being dirty.
>
Then the behavior of this feature differs from the one provided by
GIT-VERSION-GEN that is used as part of the git build process. This is
not an argument itself, but personally, I don't like this behavior, and
think it should be reconsidered before inclusion into master.
I have the following use case, which is affected. I have with in a
submodule some code that needs to be compiled, and hence generate some
object files and other files in the process. I don't want to include
these files in a .gitignore as they are named differently on different
systems. Hence, I include them in my .git/info/exclude file, where I am
developing the module. So now, unless I do the same thing for all
places I checkout the repo as submodule, I end up with the module
indicated as dirty after I compile it. This is a bit inconvenient.
Am I the only one who uses submodules this way? Is there a better way
to solve my problem that would provide a better work pattern in this case?
/Gustaf
^ permalink raw reply
* Re: Unmodified submodules shows up as dirty with 1.6.6.443.gd7346
From: Jacob Helwig @ 2010-01-18 16:02 UTC (permalink / raw)
To: Gustaf Hendeby; +Cc: git, Jens.Lehmann
In-Reply-To: <4B547EA6.5070203@isy.liu.se>
On Mon, Jan 18, 2010 at 07:30, Gustaf Hendeby <hendeby@isy.liu.se> wrote:
> Hi!
>
> I have been using submodules for a while, and been quite happy with
> them. Just updating to the latest next (1.6.6.443.gd7346), a strange
> problem has occurred. All my submodules (which are in fact unmodified)
> show as modified and dirty
>
> diff --git a/extern/utils b/extern/utils
> --- a/extern/utils
> +++ b/extern/utils
> @@ -1 +1 @@
> -Subproject commit 6bad20e1419f1ca61bd5a6eef9b5937122e006f1
> +Subproject commit 6bad20e1419f1ca61bd5a6eef9b5937122e006f1-dirty
>
>
> Anyone have an idea what is going on here? (Related to the recent topic
> by Jens Lehmann? cc:ed) Unfortunately, I don't have the time to dig
> into this right now, but I'll try to do it later tonight if the problem
> remains.
>
> /Gustaf
>
Do you have any untracked files in the submodule? git status is
working as I would expect with the same version (1.6.6.443.gd7346).
If there is no output from git status in the submodule, then git
status in the superproject shows the submodule as being clean.
However, if there is _any_ output from git status (untracked files,
modified files, deleted files, new files), then the superproject shows
the submodule as being dirty.
-Jacob
^ permalink raw reply
* Unmodified submodules shows up as dirty with 1.6.6.443.gd7346
From: Gustaf Hendeby @ 2010-01-18 15:30 UTC (permalink / raw)
To: git; +Cc: Jens.Lehmann
Hi!
I have been using submodules for a while, and been quite happy with
them. Just updating to the latest next (1.6.6.443.gd7346), a strange
problem has occurred. All my submodules (which are in fact unmodified)
show as modified and dirty
diff --git a/extern/utils b/extern/utils
--- a/extern/utils
+++ b/extern/utils
@@ -1 +1 @@
-Subproject commit 6bad20e1419f1ca61bd5a6eef9b5937122e006f1
+Subproject commit 6bad20e1419f1ca61bd5a6eef9b5937122e006f1-dirty
Anyone have an idea what is going on here? (Related to the recent topic
by Jens Lehmann? cc:ed) Unfortunately, I don't have the time to dig
into this right now, but I'll try to do it later tonight if the problem
remains.
/Gustaf
^ permalink raw reply
* Re: [PATCH v3] Threaded grep
From: Johannes Sixt @ 2010-01-18 13:45 UTC (permalink / raw)
To: Fredrik Kuivinen
Cc: git, Junio C Hamano, Linus Torvalds, Andrzej K. Haczewski
In-Reply-To: <4c8ef71001180528h6182ef4l8a617fd038bd7318@mail.gmail.com>
Fredrik Kuivinen schrieb:
> On Mon, Jan 18, 2010 at 12:11, Johannes Sixt <j.sixt@viscovery.net> wrote:
>> Fredrik Kuivinen schrieb:
>>> + pthread_mutex_unlock(&grep_lock);
>>> + pthread_cond_signal(&cond_add);
>> Please swap these two lines, so that pthread_cond_signal() is called while
>> the lock is held.
>
> May I ask why? (just curious)
Because our pthreads_cond_* functions on Windows are not POSIXly correct
and work reliably only when pthread_cond_signal() is called while the
mutex is held.
-- Hannes
^ permalink raw reply
* Re: [PATCH v3] Threaded grep
From: Fredrik Kuivinen @ 2010-01-18 13:28 UTC (permalink / raw)
To: Johannes Sixt; +Cc: git, Junio C Hamano, Linus Torvalds, Andrzej K. Haczewski
In-Reply-To: <4B5441D7.40503@viscovery.net>
On Mon, Jan 18, 2010 at 12:11, Johannes Sixt <j.sixt@viscovery.net> wrote:
> Fredrik Kuivinen schrieb:
>> +/* This lock protects all the variables above. */
>> +static pthread_mutex_t grep_lock = PTHREAD_MUTEX_INITIALIZER;
>> +
>> +/* Signalled when a new work_item is added to todo. */
>> +static pthread_cond_t cond_add = PTHREAD_COND_INITIALIZER;
>> +
>> +/* Signalled when the result from one work_item is written to
>> + stdout. */
>> +static pthread_cond_t cond_write = PTHREAD_COND_INITIALIZER;
>> +
>> +/* Signalled when we are finished with everything. */
>> +static pthread_cond_t cond_result = PTHREAD_COND_INITIALIZER;
>
> Please do not use PTHREAD_MUTEX_INITIALIZER nor PTHREAD_COND_INITIALIZER;
> call pthread_mutex_init and pthread_cond_init (and the corresponding
> *_destroy!!) from the code.
Ok. Will fix.
>> +static void add_work(enum work_type type, char *name, char *buf, size_t size)
>> +{
>> +...
>> + pthread_mutex_unlock(&grep_lock);
>> + pthread_cond_signal(&cond_add);
>
> Please swap these two lines, so that pthread_cond_signal() is called while
> the lock is held.
May I ask why? (just curious)
Thanks for your comments.
- Fredrik
^ permalink raw reply
* Re: [RFC] Git Wiki Move
From: Johannes Schindelin @ 2010-01-18 12:03 UTC (permalink / raw)
To: J.H.; +Cc: Matthieu Moy, Petr Baudis, git
In-Reply-To: <alpine.DEB.1.00.1001181044410.4985@pacific.mpi-cbg.de>
Hi,
On Mon, 18 Jan 2010, Johannes Schindelin wrote:
> On Sun, 17 Jan 2010, J.H. wrote:
>
> > On 01/17/2010 03:06 PM, Johannes Schindelin wrote:
> >
> > > BTW there is a file KHTMLFixes.css in the directory skins/monobook/
> > > which makes the layout break with Chromium. Apparently, it is no
> > > longer needed by KHTML anyway. So could you please replace that
> > > file with an empty one, or comment out the offending part, like so:
> > >
> > > /* #column-content { margin-left: 0; } */
> >
> > I'm not keen on making changes since that file is still coming from
> > the shipping version of mediawiki and I'm trying, quite a lot, to not
> > run a modified version of it. I've got enough troubles with the fact
> > that that one change would affect 22 wikis in a single go.
> >
> > As bad as it is to say this, I'd rather wait for 1.16 to come out vs.
> > modify it in place. Mediawiki is claiming they are in continuous
> > integration development with quarterly releases but their last release
> > was June of 2009, so take that as you will.
>
> Fair enough. As long as Chromium has a fairly small share of the
> market, I think it is safe to tell everybody to wait a little until the
> side bar is no longer displayed at the left _below_ the main body text.
> AFAICT the problem was solved with Wikipedia, so the next release should
> magically fix the issue.
Actually, in a rare case of cleverness, I found out how to fix it (at
least for me, it works):
http://git.wiki.kernel.org/index.php/MediaWiki:Monobook.css
Of course, this is only a work-around, and it will get broken once
Monobook changes dimensions (or more). But hopefully Chromium will be
fixed by then, extending the canvas into the negative arm of the x axis
whenever needed. And then I will happily delete the custom Monobook.css.
These are the things left that I would like to see soon:
- add a link from the old Wiki (with rewrite rules)
- use whatever logo (anything is better than the sunflower)
- enable anonymous edits
- a major cleanup of the broken autolinks and faulty formatting.
The latter point is probably the most tedious one, so I suggest that only
those get a vote on the logo who fix at least 3 pages.
Ciao,
Dscho
^ permalink raw reply
* Re: [PATCH v3] Threaded grep
From: Johannes Sixt @ 2010-01-18 11:11 UTC (permalink / raw)
To: Fredrik Kuivinen
Cc: git, Junio C Hamano, Linus Torvalds, Andrzej K. Haczewski
In-Reply-To: <20100118103334.GA17361@fredrik-laptop>
Fredrik Kuivinen schrieb:
> The patch has been rebased on top of next and I believe that it is now
> ready for inclusion. It is time to decide if the added code complexity
> is worth the increased performance.
I also have to add a few nits to make this play better on Windows.
> +/* This lock protects all the variables above. */
> +static pthread_mutex_t grep_lock = PTHREAD_MUTEX_INITIALIZER;
> +
> +/* Signalled when a new work_item is added to todo. */
> +static pthread_cond_t cond_add = PTHREAD_COND_INITIALIZER;
> +
> +/* Signalled when the result from one work_item is written to
> + stdout. */
> +static pthread_cond_t cond_write = PTHREAD_COND_INITIALIZER;
> +
> +/* Signalled when we are finished with everything. */
> +static pthread_cond_t cond_result = PTHREAD_COND_INITIALIZER;
Please do not use PTHREAD_MUTEX_INITIALIZER nor PTHREAD_COND_INITIALIZER;
call pthread_mutex_init and pthread_cond_init (and the corresponding
*_destroy!!) from the code.
> +static void add_work(enum work_type type, char *name, char *buf, size_t size)
> +{
> +...
> + pthread_mutex_unlock(&grep_lock);
> + pthread_cond_signal(&cond_add);
Please swap these two lines, so that pthread_cond_signal() is called while
the lock is held.
> + /* Wake up all the consumer threads so they can see that there
> + is no more work to do. */
> + pthread_cond_broadcast(&cond_add);
> + pthread_mutex_unlock(&grep_lock);
Ouch! We do not have pthread_cond_broadcast() on Windows, yet. This
shouldn't be a stopper for this patch, though. Perhaps Andrzej (cc:ed) can
implement it?
-- Hannes
^ permalink raw reply
* [PATCH v3] Threaded grep
From: Fredrik Kuivinen @ 2010-01-18 10:33 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Linus Torvalds
Make git grep use threads when it is available.
The results below are best of five runs in the Linux repository (on a
box with two cores).
With the patch:
git grep qwerty
1.58user 0.55system 0:01.16elapsed 183%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+800outputs (0major+5774minor)pagefaults 0swaps
Without:
git grep qwerty
1.59user 0.43system 0:02.02elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+800outputs (0major+3716minor)pagefaults 0swaps
And with a pattern with quite a few matches:
With the patch:
$ /usr/bin/time git grep void
5.61user 0.56system 0:03.44elapsed 179%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+800outputs (0major+5587minor)pagefaults 0swaps
Without:
$ /usr/bin/time git grep void
5.36user 0.51system 0:05.87elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+800outputs (0major+3693minor)pagefaults 0swaps
In either case we gain about 40% by the threading.
Signed-off-by: Fredrik Kuivinen <frekui@gmail.com>
---
The patch has been rebased on top of next and I believe that it is now
ready for inclusion. It is time to decide if the added code complexity
is worth the increased performance.
builtin-grep.c | 325 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
grep.c | 104 +++++++++++++++---
grep.h | 6 +
3 files changed, 400 insertions(+), 35 deletions(-)
diff --git a/builtin-grep.c b/builtin-grep.c
index 24ae1ce..dc07e9e 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -15,11 +15,257 @@
#include "grep.h"
#include "quote.h"
+#ifndef NO_PTHREADS
+#include "thread-utils.h"
+#include <pthread.h>
+#endif
+
static char const * const grep_usage[] = {
"git grep [options] [-e] <pattern> [<rev>...] [[--] path...]",
NULL
};
+static int use_threads = 1;
+
+#ifndef NO_PTHREADS
+#define THREADS 8
+static pthread_t threads[THREADS];
+
+static void* load_file(const char *filename, size_t *sz);
+
+enum work_type {WORK_BUF, WORK_FILE};
+
+/* We use one producer thread and number_of_threads consumer
+ threads. The producer adds struct work_items to 'todo' and the
+ consumers pick work items from the same array. */
+struct work_item
+{
+ enum work_type type;
+ char *name;
+
+ /* if type == WORK_BUF, then 'buf' points to a buffer of size
+ 'size' otherwise type == WORK_FILE and 'buf' is a NUL
+ terminated filename. */
+ char *buf;
+ size_t size;
+ char done;
+ struct strbuf out;
+};
+
+/* In the range [todo_done, todo_start) in 'todo' we have work_items
+ that have been or are processed by a consumer thread. We haven't
+ written the result for these to stdout yet.
+
+ The work_items in [todo_start, todo_end) are waiting to be picked
+ up by a consumer thread.
+
+ The ranges are modulo TODO_SIZE.
+*/
+#define TODO_SIZE 128
+static struct work_item todo[TODO_SIZE];
+static int todo_start = 0;
+static int todo_end = 0;
+static int todo_done = 0;
+
+/* Has all work items been added? */
+static int all_work_added = 0;
+
+/* This lock protects all the variables above. */
+static pthread_mutex_t grep_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* Signalled when a new work_item is added to todo. */
+static pthread_cond_t cond_add = PTHREAD_COND_INITIALIZER;
+
+/* Signalled when the result from one work_item is written to
+ stdout. */
+static pthread_cond_t cond_write = PTHREAD_COND_INITIALIZER;
+
+/* Signalled when we are finished with everything. */
+static pthread_cond_t cond_result = PTHREAD_COND_INITIALIZER;
+
+static void add_work(enum work_type type, char *name, char *buf, size_t size)
+{
+ pthread_mutex_lock(&grep_lock);
+
+ while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) {
+ pthread_cond_wait(&cond_write, &grep_lock);
+ }
+
+ todo[todo_end].type = type;
+ todo[todo_end].name = name;
+ todo[todo_end].buf = buf;
+ todo[todo_end].size = size;
+ todo[todo_end].done = 0;
+ strbuf_reset(&todo[todo_end].out);
+ todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
+
+ pthread_mutex_unlock(&grep_lock);
+ pthread_cond_signal(&cond_add);
+}
+
+static struct work_item* get_work()
+{
+ struct work_item* ret;
+
+ pthread_mutex_lock(&grep_lock);
+ while (todo_start == todo_end && !all_work_added) {
+ pthread_cond_wait(&cond_add, &grep_lock);
+ }
+
+ if (todo_start == todo_end && all_work_added) {
+ ret = NULL;
+ } else {
+ ret = &todo[todo_start];
+ todo_start = (todo_start + 1) % ARRAY_SIZE(todo);
+ }
+ pthread_mutex_unlock(&grep_lock);
+ return ret;
+}
+
+/* This function takes ownership of 'name' and 'buf'. They will be
+ deallocated with free. */
+static int grep_buffer_async(struct grep_opt *opt, char *name, char *buf,
+ size_t size)
+{
+ add_work(WORK_BUF, name, buf, size);
+ return 0;
+}
+
+static int grep_file_async(struct grep_opt *opt, char *name,
+ const char *filename)
+{
+ add_work(WORK_FILE, name, (char*) filename, 0);
+ return 0;
+}
+
+static void work_done(struct work_item *w)
+{
+ int old_done;
+
+ pthread_mutex_lock(&grep_lock);
+ w->done = 1;
+ old_done = todo_done;
+ for(; todo[todo_done].done && todo_done != todo_start;
+ todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
+ w = &todo[todo_done];
+ write_or_die(1, w->out.buf, w->out.len);
+ if (w->type == WORK_BUF)
+ free(w->buf);
+
+ free(w->name);
+ }
+
+ if (old_done != todo_done)
+ pthread_cond_signal(&cond_write);
+
+ if (all_work_added && todo_done == todo_end)
+ pthread_cond_signal(&cond_result);
+
+ pthread_mutex_unlock(&grep_lock);
+}
+
+static void* run(void *arg)
+{
+ int hit = 0;
+ struct grep_opt *opt = arg;
+
+ while (1) {
+ struct work_item *w = get_work();
+ if (!w)
+ break;
+
+ opt->output_priv = w;
+ if (w->type == WORK_BUF) {
+ hit |= grep_buffer(opt, w->name, w->buf, w->size);
+ } else {
+ size_t sz;
+ void* data = load_file(w->buf, &sz);
+ if (data) {
+ hit |= grep_buffer(opt, w->name, data, sz);
+ free(data);
+ }
+ }
+
+ work_done(w);
+ }
+
+ return (void*) (intptr_t) hit;
+}
+
+static void strbuf_out(struct grep_opt *opt, const void *buf, size_t size)
+{
+ struct work_item *w = opt->output_priv;
+ strbuf_add(&w->out, buf, size);
+}
+
+static void start_threads(struct grep_opt *opt)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(todo); i++) {
+ strbuf_init(&todo[i].out, 0);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(threads); i++) {
+ struct grep_opt *o = grep_opt_dup(opt);
+ o->output = strbuf_out;
+ compile_grep_patterns(o);
+ int err = pthread_create(&threads[i], NULL, run, o);
+
+ if (err)
+ die("grep: failed to create thread: %s", strerror(err));
+ }
+}
+#endif /* !NO_PTHREADS */
+
+#ifndef NO_PTHREADS
+static int wait_all()
+{
+ int hit = 0;
+ int i;
+
+ pthread_mutex_lock(&grep_lock);
+ all_work_added = 1;
+
+ /* Wait until all work is done. */
+ while (todo_done != todo_end)
+ pthread_cond_wait(&cond_result, &grep_lock);
+
+ /* Wake up all the consumer threads so they can see that there
+ is no more work to do. */
+ pthread_cond_broadcast(&cond_add);
+ pthread_mutex_unlock(&grep_lock);
+
+ for (i = 0; i < ARRAY_SIZE(threads); i++) {
+ void *h;
+ pthread_join(threads[i], &h);
+ hit |= (int) (intptr_t) h;
+ }
+
+ return hit;
+}
+#else
+static int wait_all()
+{
+ return 0;
+}
+#endif
+
+static int grep_buffer_internal(struct grep_opt *opt, char *name, char *buf,
+ size_t size)
+{
+#ifndef NO_PTHREADS
+ if (use_threads)
+ return grep_buffer_async(opt, name, buf, size);
+#endif
+ {
+ int hit = grep_buffer(opt, name, buf, size);
+ free(name);
+ free(buf);
+ return hit;
+ }
+}
+
static int grep_config(const char *var, const char *value, void *cb)
{
struct grep_opt *opt = cb;
@@ -159,21 +405,21 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char
if (opt->relative && opt->prefix_length) {
quote_path_relative(name + tree_name_len, -1, &pathbuf, opt->prefix);
strbuf_insert(&pathbuf, 0, name, tree_name_len);
- name = pathbuf.buf;
+ } else {
+ strbuf_addstr(&pathbuf, name);
}
- hit = grep_buffer(opt, name, data, size);
- strbuf_release(&pathbuf);
- free(data);
+
+ hit = grep_buffer_internal(opt, strbuf_detach(&pathbuf, NULL),
+ data, size);
+
return hit;
}
-static int grep_file(struct grep_opt *opt, const char *filename)
+static void* load_file(const char *filename, size_t *sz)
{
struct stat st;
+ char* data;
int i;
- char *data;
- size_t sz;
- struct strbuf buf = STRBUF_INIT;
if (lstat(filename, &st) < 0) {
err_ret:
@@ -183,24 +429,47 @@ static int grep_file(struct grep_opt *opt, const char *filename)
}
if (!S_ISREG(st.st_mode))
return 0;
- sz = xsize_t(st.st_size);
+ *sz = xsize_t(st.st_size);
i = open(filename, O_RDONLY);
if (i < 0)
goto err_ret;
- data = xmalloc(sz + 1);
- if (st.st_size != read_in_full(i, data, sz)) {
+ data = xmalloc(*sz + 1);
+ data[*sz] = 0;
+ if (st.st_size != read_in_full(i, data, *sz)) {
error("'%s': short read %s", filename, strerror(errno));
close(i);
free(data);
return 0;
}
close(i);
+ return data;
+}
+
+static int grep_file(struct grep_opt *opt, const char *filename)
+{
+ char *data;
+ size_t sz;
+ struct strbuf buf = STRBUF_INIT;
+ char *name;
+
if (opt->relative && opt->prefix_length)
- filename = quote_path_relative(filename, -1, &buf, opt->prefix);
- i = grep_buffer(opt, filename, data, sz);
- strbuf_release(&buf);
- free(data);
- return i;
+ quote_path_relative(filename, -1, &buf, opt->prefix);
+ else
+ strbuf_addstr(&buf, filename);
+ name = strbuf_detach(&buf, NULL);
+
+#ifndef NO_PTHREADS
+ if (use_threads) {
+ return grep_file_async(opt, name, filename);
+ } else
+#endif
+ {
+ data = load_file(filename, &sz);
+ if (!data)
+ return 0;
+
+ return grep_buffer_internal(opt, name, data, sz);
+ }
}
static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
@@ -546,6 +815,20 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
opt.regflags |= REG_ICASE;
if ((opt.regflags != REG_NEWLINE) && opt.fixed)
die("cannot mix --fixed-strings and regexp");
+
+#ifndef NO_PTHREADS
+ if (!grep_threads_ok(&opt))
+ use_threads = 0;
+
+ if (online_cpus() == 1)
+ use_threads = 0;
+
+ if (use_threads)
+ start_threads(&opt);
+#else
+ use_threads = 0;
+#endif
+
compile_grep_patterns(&opt);
/* Check revs and then paths */
@@ -583,9 +866,14 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
}
if (!list.nr) {
+ int hit;
if (!cached)
setup_work_tree();
- return !grep_cache(&opt, paths, cached);
+
+ hit = grep_cache(&opt, paths, cached);
+ if (use_threads)
+ hit |= wait_all();
+ return !hit;
}
if (cached)
@@ -597,6 +885,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (grep_object(&opt, paths, real_obj, list.objects[i].name))
hit = 1;
}
+
+ if (use_threads)
+ hit |= wait_all();
free_grep_patterns(&opt);
return !hit;
}
diff --git a/grep.c b/grep.c
index 8e1f7de..05aab15 100644
--- a/grep.c
+++ b/grep.c
@@ -29,6 +29,28 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat,
p->next = NULL;
}
+struct grep_opt* grep_opt_dup(const struct grep_opt *opt)
+{
+ struct grep_pat *pat;
+ struct grep_opt *ret = xmalloc(sizeof(struct grep_opt));
+ *ret = *opt;
+
+ ret->pattern_list = NULL;
+ ret->pattern_tail = &ret->pattern_list;
+
+ for(pat = opt->pattern_list; pat != NULL; pat = pat->next)
+ {
+ if(pat->token == GREP_PATTERN_HEAD)
+ append_header_grep_pattern(ret, pat->field,
+ pat->pattern);
+ else
+ append_grep_pattern(ret, pat->pattern, pat->origin,
+ pat->no, pat->token);
+ }
+
+ return ret;
+}
+
static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
{
int err;
@@ -253,7 +275,8 @@ static int word_char(char ch)
static void show_name(struct grep_opt *opt, const char *name)
{
- printf("%s%c", name, opt->null_following_name ? '\0' : '\n');
+ opt->output(opt, name, strlen(name));
+ opt->output(opt, opt->null_following_name ? "\0" : "\n", 1);
}
@@ -490,24 +513,32 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
const char *name, unsigned lno, char sign)
{
int rest = eol - bol;
+ char sign_str[1];
+ sign_str[0] = sign;
if (opt->pre_context || opt->post_context) {
if (opt->last_shown == 0) {
if (opt->show_hunk_mark)
- fputs("--\n", stdout);
+ opt->output(opt, "--\n", 3);
else
opt->show_hunk_mark = 1;
} else if (lno > opt->last_shown + 1)
- fputs("--\n", stdout);
+ opt->output(opt, "--\n", 3);
}
opt->last_shown = lno;
if (opt->null_following_name)
- sign = '\0';
- if (opt->pathname)
- printf("%s%c", name, sign);
- if (opt->linenum)
- printf("%d%c", lno, sign);
+ sign_str[0] = '\0';
+ if (opt->pathname) {
+ opt->output(opt, name, strlen(name));
+ opt->output(opt, sign_str, 1);
+ }
+ if (opt->linenum) {
+ char buf[32];
+ snprintf(buf, sizeof(buf), "%d", lno);
+ opt->output(opt, buf, strlen(buf));
+ opt->output(opt, sign_str, 1);
+ }
if (opt->color) {
regmatch_t match;
enum grep_context ctx = GREP_CONTEXT_BODY;
@@ -518,18 +549,22 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
while (next_match(opt, bol, eol, ctx, &match, eflags)) {
if (match.rm_so == match.rm_eo)
break;
- printf("%.*s%s%.*s%s",
- (int)match.rm_so, bol,
- opt->color_match,
- (int)(match.rm_eo - match.rm_so), bol + match.rm_so,
- GIT_COLOR_RESET);
+
+ opt->output(opt, bol, match.rm_so);
+ opt->output(opt, opt->color_match,
+ strlen(opt->color_match));
+ opt->output(opt, bol + match.rm_so,
+ (int)(match.rm_eo - match.rm_so));
+ opt->output(opt, GIT_COLOR_RESET,
+ strlen(GIT_COLOR_RESET));
bol += match.rm_eo;
rest -= match.rm_eo;
eflags = REG_NOTBOL;
}
*eol = ch;
}
- printf("%.*s\n", rest, bol);
+ opt->output(opt, bol, rest);
+ opt->output(opt, "\n", 1);
}
static int match_funcname(struct grep_opt *opt, char *bol, char *eol)
@@ -667,6 +702,30 @@ static int look_ahead(struct grep_opt *opt,
return 0;
}
+int grep_threads_ok(const struct grep_opt *opt)
+{
+ /* If this condition is true, then we may use the attribute
+ machinery in grep_buffer_1. The attribute code is not
+ thread safe, so we disable the use of threads. */
+ if (opt->funcname && !opt->unmatch_name_only && !opt->status_only &&
+ !opt->name_only)
+ return 0;
+
+ /* If we are showing hunk marks, we should not do it for the
+ first match. The synchronization problem we get for this
+ constraint is not yet solved, so we disable threading in
+ this case. */
+ if (opt->pre_context || opt->post_context)
+ return 0;
+
+ return 1;
+}
+
+static void std_output(struct grep_opt *opt, const void* buf, size_t size)
+{
+ fwrite(buf, size, 1, stdout);
+}
+
static int grep_buffer_1(struct grep_opt *opt, const char *name,
char *buf, unsigned long size, int collect_hits)
{
@@ -682,6 +741,9 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
opt->last_shown = 0;
+ if (!opt->output)
+ opt->output = std_output;
+
if (buffer_is_binary(buf, size)) {
switch (opt->binary) {
case GREP_BINARY_DEFAULT:
@@ -754,7 +816,9 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
if (opt->status_only)
return 1;
if (binary_match_only) {
- printf("Binary file %s matches\n", name);
+ opt->output(opt, "Binary file ", 12);
+ opt->output(opt, name, strlen(name));
+ opt->output(opt, " matches\n", 9);
return 1;
}
if (opt->name_only) {
@@ -810,9 +874,13 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
* which feels mostly useless but sometimes useful. Maybe
* make it another option? For now suppress them.
*/
- if (opt->count && count)
- printf("%s%c%u\n", name,
- opt->null_following_name ? '\0' : ':', count);
+ if (opt->count && count) {
+ char buf[32];
+ opt->output(opt, name, strlen(name));
+ snprintf(buf, sizeof(buf), "%c%u\n",
+ opt->null_following_name ? '\0' : ':', count);
+ opt->output(opt, buf, strlen(buf));
+ }
return !!last_hit;
}
diff --git a/grep.h b/grep.h
index 0c61b00..6517909 100644
--- a/grep.h
+++ b/grep.h
@@ -91,6 +91,9 @@ struct grep_opt {
unsigned last_shown;
int show_hunk_mark;
void *priv;
+
+ void (*output)(struct grep_opt*, const void* data, size_t size);
+ void *output_priv;
};
extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);
@@ -99,4 +102,7 @@ extern void compile_grep_patterns(struct grep_opt *opt);
extern void free_grep_patterns(struct grep_opt *opt);
extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
+extern struct grep_opt* grep_opt_dup(const struct grep_opt *opt);
+extern int grep_threads_ok(const struct grep_opt *opt);
+
#endif
^ permalink raw reply related
* Re: [PATCH] builtin-apply.c: Skip filenames without enough components
From: Nanako Shiraishi @ 2010-01-18 10:22 UTC (permalink / raw)
To: Andreas Gruenbacher; +Cc: Junio C Hamano, git
In-Reply-To: <201001170344.34525.agruen@suse.de>
Quoting Andreas Gruenbacher <agruen@suse.de>
> On Sunday 17 January 2010 03:22:10 am Junio C Hamano wrote:
>> Tests?
>
> Sure if you think it's worth a regression test ...
Of course you must have a test when you are fixing things. Tests aren't to prove that your patch is correct. They are to prevent other people from breaking your change long after you leave the project.
> Sure if you think it's worth a regression test ... "git apply -p2" of the
> following patch fails...
You do so by sending a patch to add your new test in t/; adding to an existing related test is preferred if the test is small.
Junio, in case you don't want to wait for Andreas, you can squash this test in.
diff --git a/t/t4120-apply-popt.sh b/t/t4120-apply-popt.sh
index 83d4ba6..b463b4f 100755
--- a/t/t4120-apply-popt.sh
+++ b/t/t4120-apply-popt.sh
@@ -22,4 +22,9 @@ test_expect_success 'apply git diff with -p2' '
git apply -p2 patch.file
'
+test_expect_success 'apply with too large -p' '
+ test_must_fail git apply --stat -p3 patch.file 2>err &&
+ grep "removing 3 leading" err
+'
+
test_done
--
Nanako Shiraishi
http://ivory.ap.teacup.com/nanako3/
^ permalink raw reply related
* Re: [PATCH/RFC] Allow empty commits during rebase -i
From: Johannes Schindelin @ 2010-01-18 10:01 UTC (permalink / raw)
To: Pete Harlan; +Cc: Junio C Hamano, git list
In-Reply-To: <4B53C355.1010109@pcharlan.com>
Hi,
On Sun, 17 Jan 2010, Pete Harlan wrote:
> If the introduction of empty commits that the user has asked for
> (perhaps inadvertently) is considered too undesirable, then perhaps my
> fix is too simple. I'll think about how to do something more
> sophisticated.
How about something less sophisticated instead? Namely, check for the
condition that nothing was changed, and tell the user that the commit
blablabla seems to introduce changes that are already present in HEAD.
Maybe even mention that this may be due to the commit being applied
already and saying that --continue is safe in that case, but please check.
Hmm?
Ciao,
Dscho
^ permalink raw reply
* [PATCH v3] rev-parse --namespace
From: Ilari Liusvaara @ 2010-01-18 9:51 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano
In-Reply-To: <7vk4vfsv64.fsf@alter.siamese.dyndns.org>
Add --namespace=<namespace> option to rev-parse and everything that
accepts its options. This option matches all refs in some subnamespace
of refs hierarchy.
Example:
'git log --branches --not --namespace=remotes/origin'
To show what you have that origin doesn't.
Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
---
Changes from v2:
- Actually include updated git log examples (oops).
- Get rid of for_each_namespace_request, use parameters instead.
- Get rid of extraneous comment.
Documentation/git-log.txt | 6 ++
Documentation/git-rev-list.txt | 1 +
Documentation/git-rev-parse.txt | 4 +
Documentation/rev-list-options.txt | 6 ++
builtin-rev-parse.c | 5 ++
refs.c | 16 +++++
refs.h | 1 +
revision.c | 16 +++++-
t/t6018-rev-list-namespace.sh | 112 ++++++++++++++++++++++++++++++++++++
9 files changed, 165 insertions(+), 2 deletions(-)
create mode 100755 t/t6018-rev-list-namespace.sh
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 3d79de1..d84660c 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -107,6 +107,12 @@ git log --follow builtin-rev-list.c::
those commits that occurred before the file was given its
present name.
+git log --branches --not --namespace=remotes/origin::
+
+ Shows all commits that are in any of local branches but not in
+ any of remote tracking branches for 'origin' (what you have that
+ origin doesn't).
+
Discussion
----------
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index 3341d1b..a8f8f22 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -24,6 +24,7 @@ SYNOPSIS
[ \--branches ]
[ \--tags ]
[ \--remotes ]
+ [ \--namespace=namespace-prefix ]
[ \--stdin ]
[ \--quiet ]
[ \--topo-order ]
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 82045a2..af4605a 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -112,6 +112,10 @@ OPTIONS
--remotes::
Show tag refs found in `$GIT_DIR/refs/remotes`.
+--namespace=namespace-prefix::
+ Show refs found in `$GIT_DIR/namespace-prefix`. If namespace
+ specified lacks leading 'refs/', it is automatically prepended.
+
--show-prefix::
When the command is invoked from a subdirectory, show the
path of the current directory relative to the top-level
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 1f57aed..c824a7b 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -243,6 +243,12 @@ endif::git-rev-list[]
Pretend as if all the refs in `$GIT_DIR/refs/remotes` are listed
on the command line as '<commit>'.
+--namespace=namespace-prefix::
+ Pretend as if all the refs in `$GIT_DIR/namespace-prefix` are
+ listed on the command line as '<commit>'. Leading 'refs/', it
+ is automatically prepended if missing.
+
+
ifndef::git-rev-list[]
--bisect::
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index 37d0233..34af347 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -52,6 +52,7 @@ static int is_rev_argument(const char *arg)
"--parents",
"--pretty",
"--remotes",
+ "--namespace=",
"--sparse",
"--tags",
"--topo-order",
@@ -577,6 +578,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
for_each_tag_ref(show_reference, NULL);
continue;
}
+ if (!prefixcmp(arg, "--namespace=")) {
+ for_each_namespace_ref(show_reference, arg + 12, NULL);
+ continue;
+ }
if (!strcmp(arg, "--remotes")) {
for_each_remote_ref(show_reference, NULL);
continue;
diff --git a/refs.c b/refs.c
index 3e73a0a..5583f4b 100644
--- a/refs.c
+++ b/refs.c
@@ -674,6 +674,22 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data);
}
+int for_each_namespace_ref(each_ref_fn fn, const char *ns_name, void *cb_data)
+{
+ struct strbuf real_prefix = STRBUF_INIT;
+ int ret;
+
+ if (prefixcmp(ns_name, "refs/"))
+ strbuf_addstr(&real_prefix, "refs/");
+ strbuf_addstr(&real_prefix, ns_name);
+ if (real_prefix.buf[real_prefix.len - 1] != '/')
+ strbuf_addch(&real_prefix, '/');
+
+ ret = for_each_ref_in(real_prefix.buf, fn, cb_data);
+ strbuf_release(&real_prefix);
+ return ret;
+}
+
int for_each_rawref(each_ref_fn fn, void *cb_data)
{
return do_for_each_ref("refs/", fn, 0,
diff --git a/refs.h b/refs.h
index e141991..b26c3a8 100644
--- a/refs.h
+++ b/refs.h
@@ -25,6 +25,7 @@ extern int for_each_tag_ref(each_ref_fn, void *);
extern int for_each_branch_ref(each_ref_fn, void *);
extern int for_each_remote_ref(each_ref_fn, void *);
extern int for_each_replace_ref(each_ref_fn, void *);
+extern int for_each_namespace_ref(each_ref_fn, const char* ns_name, void *);
/* can be used to learn about broken ref and symref */
extern int for_each_rawref(each_ref_fn, void *);
diff --git a/revision.c b/revision.c
index 25fa14d..7328201 100644
--- a/revision.c
+++ b/revision.c
@@ -699,12 +699,18 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag,
return 0;
}
+static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,
+ unsigned flags)
+{
+ cb->all_revs = revs;
+ cb->all_flags = flags;
+}
+
static void handle_refs(struct rev_info *revs, unsigned flags,
int (*for_each)(each_ref_fn, void *))
{
struct all_refs_cb cb;
- cb.all_revs = revs;
- cb.all_flags = flags;
+ init_all_refs_cb(&cb, revs, flags);
for_each(handle_one_ref, &cb);
}
@@ -1352,6 +1358,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
handle_refs(revs, flags, for_each_remote_ref);
continue;
}
+ if (!prefixcmp(arg, "--namespace=")) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, flags);
+ for_each_namespace_ref(handle_one_ref, arg + 12, &cb);
+ continue;
+ }
if (!strcmp(arg, "--reflog")) {
handle_reflog(revs, flags);
continue;
diff --git a/t/t6018-rev-list-namespace.sh b/t/t6018-rev-list-namespace.sh
new file mode 100755
index 0000000..6bb562a
--- /dev/null
+++ b/t/t6018-rev-list-namespace.sh
@@ -0,0 +1,112 @@
+#!/bin/sh
+
+test_description='rev-list/rev-parse --namespace'
+
+. ./test-lib.sh
+
+
+commit () {
+ test_tick &&
+ echo $1 > foo &&
+ git add foo &&
+ git commit -m "$1"
+}
+
+compare () {
+ # Split arguments on whitespace.
+ git $1 $2 | sort >expected &&
+ git $1 $3 | sort >actual &&
+ cmp expected actual
+}
+
+test_expect_success 'setup' '
+
+ commit master &&
+ git checkout -b subspace/one master
+ commit one &&
+ git checkout -b subspace/two master
+ commit two &&
+ git checkout -b subspace-x master
+ commit subspace-x &&
+ git checkout -b other/three master
+ commit three &&
+ git checkout -b someref master
+ commit some &&
+ git checkout master &&
+ commit master2
+'
+
+test_expect_success 'rev-parse --namespace=refs/heads/subspace/' '
+
+ compare rev-parse "subspace/one subspace/two" "--namespace=refs/heads/subspace/"
+
+'
+
+test_expect_success 'rev-parse --namespace=refs/heads/subspace' '
+
+ compare rev-parse "subspace/one subspace/two" "--namespace=refs/heads/subspace"
+
+'
+
+test_expect_success 'rev-parse --namespace=heads/subspace' '
+
+ compare rev-parse "subspace/one subspace/two" "--namespace=heads/subspace"
+
+'
+
+test_expect_success 'rev-parse --namespace=heads/subspace --namespace=heads/other' '
+
+ compare rev-parse "subspace/one subspace/two other/three" "--namespace=heads/subspace --namespace=heads/other"
+
+'
+
+test_expect_success 'rev-parse --namespace=heads/someref master' '
+
+ compare rev-parse "master" "--namespace=heads/someref master"
+
+'
+
+test_expect_success 'rev-parse --namespace=heads' '
+
+ compare rev-parse "subspace/one subspace/two other/three subspace-x master someref" "--namespace=heads"
+
+'
+
+test_expect_success 'rev-list --namespace=refs/heads/subspace/' '
+
+ compare rev-list "subspace/one subspace/two" "--namespace=refs/heads/subspace/"
+
+'
+
+test_expect_success 'rev-list --namespace=refs/heads/subspace' '
+
+ compare rev-list "subspace/one subspace/two" "--namespace=refs/heads/subspace"
+
+'
+
+test_expect_success 'rev-list --namespace=heads/subspace' '
+
+ compare rev-list "subspace/one subspace/two" "--namespace=heads/subspace"
+
+'
+
+test_expect_success 'rev-list --namespace=heads/someref master' '
+
+ compare rev-parse "master" "--namespace=heads/someref master"
+
+'
+
+test_expect_success 'rev-list --namespace=heads/subspace --namespace=heads/other' '
+
+ compare rev-parse "subspace/one subspace/two other/three" "--namespace=heads/subspace --namespace=heads/other"
+
+'
+
+test_expect_success 'rev-list --namespace=heads' '
+
+ compare rev-parse "subspace/one subspace/two other/three subspace-x master someref" "--namespace=heads"
+
+'
+
+
+test_done
--
1.6.6.199.gff4b0
^ permalink raw reply related
* Re: idea: git "came from" tags
From: Michael J Gruber @ 2010-01-18 9:49 UTC (permalink / raw)
To: D Herring; +Cc: git
In-Reply-To: <hj0nl9$uds$2@ger.gmane.org>
D Herring venit, vidit, dixit 18.01.2010 05:22:
> Actors:
> - public "upstream" repository
> - public "local" repository
> - end users tracking both
>
> Situation:
> - local starts by tracking upstream
> - local makes changes, commits, and sends upstream
> - users now tracking local ahead of upstream
Here I have to ask why. If users choose to track a volatile branch then
they have to live with rebasing or hard resets. If they want something
stable then they should track upstream.
> - upstream makes modified commits
> - local satisfied, wants to reset master to upstream/master
>
> Problem:
> - A merge will perpetually leave two parallel branches. Even though
> there are no longer any diffs, local/master cannot use the same
> objects as upstream/master.
If there are no diffs then, in fact, it can share most objects since
most trees will be the same, only a few commit objects will differ.
> - A hard reset lets local/master return to sharing objects with
> upstream/master; but this may break pulls or cause other problems for
> users.
>
> Proposed solution:
> - Local adds a "came from" tag to upstream/master, leaves a tag on the
> head of local/master, and does a hard reset from local/master to
> upstream/master. When a user tracking local/master does a pull, their
> client detects a non-fast-forward, finds the came-from tag, and treats
> it as a fast-forward.
>
> Basically, this is a protocol to glue a "strategy ours" merge onto an
> existing tree. This way local can cleanly track upstream, with no
> added complexity in the nominal (no local changes) case.
But doesn't that mean that users completely trust you about what they
should consider a fast forward, i.e. when they should do a hard reset?
So, this is completely equivalent to following one of your branches with
+f, i.e. having a public a branch which they pull from no matter what,
and having a private branch which pushes to the public one in case of
fast-forwards as well as in the case when you would use your special tag.
Michael
^ permalink raw reply
* Re: [RFC] Git Wiki Move
From: Johannes Schindelin @ 2010-01-18 9:53 UTC (permalink / raw)
To: J.H.; +Cc: Matthieu Moy, Petr Baudis, git
In-Reply-To: <4B53AEAC.6060100@eaglescrag.net>
Hi,
On Sun, 17 Jan 2010, J.H. wrote:
> On 01/17/2010 03:06 PM, Johannes Schindelin wrote:
>
> > On Sun, 17 Jan 2010, Matthieu Moy wrote:
> >
> >> You should set $wgLogo to some Git logo, among
> >> http://git.or.cz/gitwiki/GitRelatedLogos
> >
> > Best would be the standard one: http://git.or.cz/git-logo.png but I
> > would actually also like http://henrik.nyh.se/uploads/git-logo.png.
>
> I'd leave that up to the greater community. The second one there would
> fit better on the page honestly, but I don't want to go making a logo
> decision for the entirety of the project.
Why not set the logo to the second one, and at the same time start one of
our famous bikeshedding fests? (Anyone answering to that question, please
change the subject to "Wiki logo".)
> >> You can also add a few links to the sidebar, by editting:
> >> http://git.wiki.kernel.org/index.php/MediaWiki:Sidebar
> >>
> >> (it seems I don't have permission to do it myself).
> >
> > You need to be in the Sysop list to do so. Warthog, if you trust me :-)
> > would you mind adding me to that group? You can do that by visiting
> >
> > http://git.wiki.kernel.org/index.php/Special:Userrights
> >
> > when you are a sysop.
>
> Done.
Thank you very much!
> >> I suggest taking the ones of the front-page:
> >>
> >> * Starting points
> >> ** Installation|Installation
> >> ** InterfacesFrontendsAndTools|Git Tools
> >> ** GitDocumentation|Git Documentation
> >> ** GitCommunity|Git Community Support
> >> ** GitProjects|Projects using Git
> >> ** GitFaq|FAQ
> >> ** GitHosting|Git Hosting
> >> ** GitLinks|Git Links
> >> ** GitComparison|Git Compared
> >
> > Let's not forget
> >
> > * navigation
> > ** mainpage|mainpage-description
> > ** recentchanges-url|recentchanges
> > ** randompage-url|randompage
> > ** helppage|help
> > * SEARCH
> > * TOOLBOX
Changed as suggested.
> > BTW there is a file KHTMLFixes.css in the directory skins/monobook/
> > which makes the layout break with Chromium. Apparently, it is no
> > longer needed by KHTML anyway. So could you please replace that file
> > with an empty one, or comment out the offending part, like so:
> >
> > /* #column-content { margin-left: 0; } */
>
> I'm not keen on making changes since that file is still coming from the
> shipping version of mediawiki and I'm trying, quite a lot, to not run a
> modified version of it. I've got enough troubles with the fact that
> that one change would affect 22 wikis in a single go.
>
> As bad as it is to say this, I'd rather wait for 1.16 to come out vs.
> modify it in place. Mediawiki is claiming they are in continuous
> integration development with quarterly releases but their last release
> was June of 2009, so take that as you will.
Fair enough. As long as Chromium has a fairly small share of the market,
I think it is safe to tell everybody to wait a little until the side bar
is no longer displayed at the left _below_ the main body text. AFAICT the
problem was solved with Wikipedia, so the next release should magically
fix the issue.
Thanks,
Dscho
^ permalink raw reply
* Re: ambiguous argument '...': unknown revision or path not in the working tree
From: Sven Joachim @ 2010-01-18 8:48 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7v3a23ucdm.fsf@alter.siamese.dyndns.org>
On 2010-01-18 07:59 +0100, Junio C Hamano wrote:
> Sven Joachim <svenjoac@gmx.de> writes:
>
>> I've got a strange error message in 'git push':
>>
>> ,----
>> | % git push
>> | Counting objects: 47, done.
>> | Delta compression using up to 2 threads.
>> | Compressing objects: 100% (24/24), done.
>> | Writing objects: 100% (24/24), 4.37 KiB, done.
>> | Total 24 (delta 22), reused 0 (delta 0)
>> | fatal: ambiguous argument '3bbc6def8a06e4411bee130b811ff9507e90503d:debian/changelog': unknown revision or path not in the working tree.
>> | Use '--' to separate paths from revisions
>
> I think there is nothing "fatal" in your git setup, other than that the
> repository at the remote end you are pushing into has a broken hook script
> (perhaps update, post-receive, or post-update) that is issuing the error
> message.
Thanks, that hits the nail on the head.
> I would raise the issue with the owner of the repository if I were you.
I've opened a support ticket.
Sven
^ permalink raw reply
* Re: [PATCH v2] rev-parse --namespace
From: Junio C Hamano @ 2010-01-18 7:56 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: git
In-Reply-To: <1263798952-27624-1-git-send-email-ilari.liusvaara@elisanet.fi>
Ilari Liusvaara <ilari.liusvaara@elisanet.fi> writes:
> Add --namespace=<namespace> option to rev-parse and everything that
> accepts its options. This option matches all refs in some subnamespace
> of refs hierarchy.
>
> Example:
>
> 'git log --branches --not --namespace=remotes/origin'
>
> To show what you have that origin doesn't.
>
> Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
Thanks.
> @@ -577,6 +578,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
> for_each_tag_ref(show_reference, NULL);
> continue;
> }
> + if (!prefixcmp(arg, "--namespace=")) {
> + struct for_each_namespace_request req;
> + req.req_namespace = arg + 12;
> + req.req_opaque = NULL;
If the structure variable is often named after "request", there is not
much point naming its fields (which are most likely all related to
request) with "req_" prefix. IOW, wouldn't this be much easier to read?
struct for_each_namespace_request req;
memset(&req, 0, sizeof(req));
req.namespace = arg + 12;
But please don't worry about this part too much, as I'll suggest getting
rid of the struct altogether shortly (read on).
> diff --git a/refs.c b/refs.c
> index 3e73a0a..c7162d1 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -7,6 +7,7 @@
> /* ISSYMREF=01 and ISPACKED=02 are public interfaces */
> #define REF_KNOWS_PEELED 04
>
> +/* Current prefix namespace in use. NULL means none. */
> struct ref_list {
> struct ref_list *next;
> unsigned char flag; /* ISSYMREF? ISPACKED? */
Is the above new comment really about what this structure type is?
> diff --git a/refs.h b/refs.h
> index e141991..4bb63b0 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -25,6 +37,7 @@ extern int for_each_tag_ref(each_ref_fn, void *);
> extern int for_each_branch_ref(each_ref_fn, void *);
> extern int for_each_remote_ref(each_ref_fn, void *);
> extern int for_each_replace_ref(each_ref_fn, void *);
> +extern int for_each_namespace_ref(each_ref_fn, void *);
I know somebody in the review discussion thought that the second parameter
for this should be void * for whatever reason, but I don't see the point.
Why cannot this be
for_each_namespace_ref(each_ref_fn, const char *, void *)
where the second one specifies the "namespace prefix"?
The caller of for_each_namespace_ref() with v2 interface needs to be aware
of that for_each_namespace_request structure anyway, so there is nothing
"void *" about it. You can have a function pointer that can point at
for_each_{branch,remote,replace}_ref and indirectly call one of the
functions via that pointer to grab different set of refs, but this new
function for_each_namespace_ref cannot play together with them, as its
cb_data must be of different shape for the request to make sense; having
the same function signature as others wouldn't help.
If the callback "each_ref_fn" function learns what ns-prefix was given for
it to be called (iow, it gets the whole for_each_namespace_request,
instead of just its req_opaque member), then I might be talked into
believing that it _could_ be useful in some situations, but the way the
callback is called is just the usual "this ref points at this object name,
by the way here is the message my caller told me to give it to you". And
if the caller really wanted to tell the namespace prefix to the callback
function, it can be made part of the application specific cbdata.
So in short, I simply don't see the point of using this new "request"
structure. Here is a suggested update that can be squashed on top of v2.
Am I missing some subtle issues?
builtin-rev-parse.c | 5 +----
refs.c | 10 ++++------
refs.h | 14 +-------------
revision.c | 5 +----
4 files changed, 7 insertions(+), 27 deletions(-)
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index 255191d..34af347 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -579,10 +579,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
continue;
}
if (!prefixcmp(arg, "--namespace=")) {
- struct for_each_namespace_request req;
- req.req_namespace = arg + 12;
- req.req_opaque = NULL;
- for_each_namespace_ref(show_reference, &req);
+ for_each_namespace_ref(show_reference, arg + 12, NULL);
continue;
}
if (!strcmp(arg, "--remotes")) {
diff --git a/refs.c b/refs.c
index c7162d1..2bf8755 100644
--- a/refs.c
+++ b/refs.c
@@ -7,7 +7,6 @@
/* ISSYMREF=01 and ISPACKED=02 are public interfaces */
#define REF_KNOWS_PEELED 04
-/* Current prefix namespace in use. NULL means none. */
struct ref_list {
struct ref_list *next;
unsigned char flag; /* ISSYMREF? ISPACKED? */
@@ -675,19 +674,18 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data);
}
-int for_each_namespace_ref(each_ref_fn fn, void *cb_data)
+int for_each_namespace_ref(each_ref_fn fn, const char *ns_prefix, void *cb_data)
{
- struct for_each_namespace_request *req = cb_data;
struct strbuf real_prefix = STRBUF_INIT;
int ret;
- if (prefixcmp(req->req_namespace, "refs/"))
+ if (prefixcmp(ns_prefix, "refs/"))
strbuf_addstr(&real_prefix, "refs/");
- strbuf_addstr(&real_prefix, req->req_namespace);
+ strbuf_addstr(&real_prefix, ns_prefix);
if (real_prefix.buf[real_prefix.len - 1] != '/')
strbuf_addch(&real_prefix, '/');
- ret = for_each_ref_in(real_prefix.buf, fn, req->req_opaque);
+ ret = for_each_ref_in(real_prefix.buf, fn, cb_data);
strbuf_release(&real_prefix);
return ret;
}
diff --git a/refs.h b/refs.h
index 4bb63b0..a1d7378 100644
--- a/refs.h
+++ b/refs.h
@@ -13,18 +13,6 @@ struct ref_lock {
#define REF_ISSYMREF 01
#define REF_ISPACKED 02
-/* Opaque request for for_each_namespace_ref */
-struct for_each_namespace_request
-{
- /*
- * The limiting namespace. 'refs/' and '/' are autoprepended /
- * autoappended if missing.
- */
- const char *req_namespace;
- /* The real opaque data for callback function. */
- void *req_opaque;
-};
-
/*
* Calls the specified function for each ref file until it returns nonzero,
* and returns the value
@@ -37,7 +25,7 @@ extern int for_each_tag_ref(each_ref_fn, void *);
extern int for_each_branch_ref(each_ref_fn, void *);
extern int for_each_remote_ref(each_ref_fn, void *);
extern int for_each_replace_ref(each_ref_fn, void *);
-extern int for_each_namespace_ref(each_ref_fn, void *);
+extern int for_each_namespace_ref(each_ref_fn, const char *, void *);
/* can be used to learn about broken ref and symref */
extern int for_each_rawref(each_ref_fn, void *);
diff --git a/revision.c b/revision.c
index ff9484f..ec63fa0 100644
--- a/revision.c
+++ b/revision.c
@@ -1359,13 +1359,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
continue;
}
if (!prefixcmp(arg, "--namespace=")) {
- struct for_each_namespace_request req;
struct all_refs_cb cb;
init_all_refs_cb(&cb, revs, flags);
- req.req_namespace = arg + 12;
- req.req_opaque = &cb;
- for_each_namespace_ref(handle_one_ref, &req);
+ for_each_namespace_ref(handle_one_ref, arg + 12, &cb);
continue;
}
if (!strcmp(arg, "--reflog")) {
^ permalink raw reply related
* [PATCH v2] rev-parse --namespace
From: Ilari Liusvaara @ 2010-01-18 7:15 UTC (permalink / raw)
To: git
Add --namespace=<namespace> option to rev-parse and everything that
accepts its options. This option matches all refs in some subnamespace
of refs hierarchy.
Example:
'git log --branches --not --namespace=remotes/origin'
To show what you have that origin doesn't.
Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
---
Changes from v1:
- Get rid of global variable for namespace.
- Use strbuf API to build the namespace
- Test rev-list/rev-parse outputs up to permutation, not just line
count.
- Add example to commit message and to git log manual page.
I left renaming the option (more opinions on that?) and changing the
description in manpage (the other similar entries should be fixed too
then) for later iteration (if these are to be done).
Documentation/git-rev-list.txt | 1 +
Documentation/git-rev-parse.txt | 4 +
Documentation/rev-list-options.txt | 6 ++
builtin-rev-parse.c | 8 +++
refs.c | 18 ++++++
refs.h | 13 ++++
revision.c | 20 ++++++-
t/t6018-rev-list-namespace.sh | 112 ++++++++++++++++++++++++++++++++++++
8 files changed, 180 insertions(+), 2 deletions(-)
create mode 100755 t/t6018-rev-list-namespace.sh
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index 3341d1b..a8f8f22 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -24,6 +24,7 @@ SYNOPSIS
[ \--branches ]
[ \--tags ]
[ \--remotes ]
+ [ \--namespace=namespace-prefix ]
[ \--stdin ]
[ \--quiet ]
[ \--topo-order ]
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 82045a2..af4605a 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -112,6 +112,10 @@ OPTIONS
--remotes::
Show tag refs found in `$GIT_DIR/refs/remotes`.
+--namespace=namespace-prefix::
+ Show refs found in `$GIT_DIR/namespace-prefix`. If namespace
+ specified lacks leading 'refs/', it is automatically prepended.
+
--show-prefix::
When the command is invoked from a subdirectory, show the
path of the current directory relative to the top-level
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 1f57aed..c824a7b 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -243,6 +243,12 @@ endif::git-rev-list[]
Pretend as if all the refs in `$GIT_DIR/refs/remotes` are listed
on the command line as '<commit>'.
+--namespace=namespace-prefix::
+ Pretend as if all the refs in `$GIT_DIR/namespace-prefix` are
+ listed on the command line as '<commit>'. Leading 'refs/', it
+ is automatically prepended if missing.
+
+
ifndef::git-rev-list[]
--bisect::
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index 37d0233..255191d 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -52,6 +52,7 @@ static int is_rev_argument(const char *arg)
"--parents",
"--pretty",
"--remotes",
+ "--namespace=",
"--sparse",
"--tags",
"--topo-order",
@@ -577,6 +578,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
for_each_tag_ref(show_reference, NULL);
continue;
}
+ if (!prefixcmp(arg, "--namespace=")) {
+ struct for_each_namespace_request req;
+ req.req_namespace = arg + 12;
+ req.req_opaque = NULL;
+ for_each_namespace_ref(show_reference, &req);
+ continue;
+ }
if (!strcmp(arg, "--remotes")) {
for_each_remote_ref(show_reference, NULL);
continue;
diff --git a/refs.c b/refs.c
index 3e73a0a..c7162d1 100644
--- a/refs.c
+++ b/refs.c
@@ -7,6 +7,7 @@
/* ISSYMREF=01 and ISPACKED=02 are public interfaces */
#define REF_KNOWS_PEELED 04
+/* Current prefix namespace in use. NULL means none. */
struct ref_list {
struct ref_list *next;
unsigned char flag; /* ISSYMREF? ISPACKED? */
@@ -674,6 +675,23 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data);
}
+int for_each_namespace_ref(each_ref_fn fn, void *cb_data)
+{
+ struct for_each_namespace_request *req = cb_data;
+ struct strbuf real_prefix = STRBUF_INIT;
+ int ret;
+
+ if (prefixcmp(req->req_namespace, "refs/"))
+ strbuf_addstr(&real_prefix, "refs/");
+ strbuf_addstr(&real_prefix, req->req_namespace);
+ if (real_prefix.buf[real_prefix.len - 1] != '/')
+ strbuf_addch(&real_prefix, '/');
+
+ ret = for_each_ref_in(real_prefix.buf, fn, req->req_opaque);
+ strbuf_release(&real_prefix);
+ return ret;
+}
+
int for_each_rawref(each_ref_fn fn, void *cb_data)
{
return do_for_each_ref("refs/", fn, 0,
diff --git a/refs.h b/refs.h
index e141991..4bb63b0 100644
--- a/refs.h
+++ b/refs.h
@@ -13,6 +13,18 @@ struct ref_lock {
#define REF_ISSYMREF 01
#define REF_ISPACKED 02
+/* Opaque request for for_each_namespace_ref */
+struct for_each_namespace_request
+{
+ /*
+ * The limiting namespace. 'refs/' and '/' are autoprepended /
+ * autoappended if missing.
+ */
+ const char *req_namespace;
+ /* The real opaque data for callback function. */
+ void *req_opaque;
+};
+
/*
* Calls the specified function for each ref file until it returns nonzero,
* and returns the value
@@ -25,6 +37,7 @@ extern int for_each_tag_ref(each_ref_fn, void *);
extern int for_each_branch_ref(each_ref_fn, void *);
extern int for_each_remote_ref(each_ref_fn, void *);
extern int for_each_replace_ref(each_ref_fn, void *);
+extern int for_each_namespace_ref(each_ref_fn, void *);
/* can be used to learn about broken ref and symref */
extern int for_each_rawref(each_ref_fn, void *);
diff --git a/revision.c b/revision.c
index 25fa14d..ff9484f 100644
--- a/revision.c
+++ b/revision.c
@@ -699,12 +699,18 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag,
return 0;
}
+static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,
+ unsigned flags)
+{
+ cb->all_revs = revs;
+ cb->all_flags = flags;
+}
+
static void handle_refs(struct rev_info *revs, unsigned flags,
int (*for_each)(each_ref_fn, void *))
{
struct all_refs_cb cb;
- cb.all_revs = revs;
- cb.all_flags = flags;
+ init_all_refs_cb(&cb, revs, flags);
for_each(handle_one_ref, &cb);
}
@@ -1352,6 +1358,16 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
handle_refs(revs, flags, for_each_remote_ref);
continue;
}
+ if (!prefixcmp(arg, "--namespace=")) {
+ struct for_each_namespace_request req;
+ struct all_refs_cb cb;
+
+ init_all_refs_cb(&cb, revs, flags);
+ req.req_namespace = arg + 12;
+ req.req_opaque = &cb;
+ for_each_namespace_ref(handle_one_ref, &req);
+ continue;
+ }
if (!strcmp(arg, "--reflog")) {
handle_reflog(revs, flags);
continue;
diff --git a/t/t6018-rev-list-namespace.sh b/t/t6018-rev-list-namespace.sh
new file mode 100755
index 0000000..6bb562a
--- /dev/null
+++ b/t/t6018-rev-list-namespace.sh
@@ -0,0 +1,112 @@
+#!/bin/sh
+
+test_description='rev-list/rev-parse --namespace'
+
+. ./test-lib.sh
+
+
+commit () {
+ test_tick &&
+ echo $1 > foo &&
+ git add foo &&
+ git commit -m "$1"
+}
+
+compare () {
+ # Split arguments on whitespace.
+ git $1 $2 | sort >expected &&
+ git $1 $3 | sort >actual &&
+ cmp expected actual
+}
+
+test_expect_success 'setup' '
+
+ commit master &&
+ git checkout -b subspace/one master
+ commit one &&
+ git checkout -b subspace/two master
+ commit two &&
+ git checkout -b subspace-x master
+ commit subspace-x &&
+ git checkout -b other/three master
+ commit three &&
+ git checkout -b someref master
+ commit some &&
+ git checkout master &&
+ commit master2
+'
+
+test_expect_success 'rev-parse --namespace=refs/heads/subspace/' '
+
+ compare rev-parse "subspace/one subspace/two" "--namespace=refs/heads/subspace/"
+
+'
+
+test_expect_success 'rev-parse --namespace=refs/heads/subspace' '
+
+ compare rev-parse "subspace/one subspace/two" "--namespace=refs/heads/subspace"
+
+'
+
+test_expect_success 'rev-parse --namespace=heads/subspace' '
+
+ compare rev-parse "subspace/one subspace/two" "--namespace=heads/subspace"
+
+'
+
+test_expect_success 'rev-parse --namespace=heads/subspace --namespace=heads/other' '
+
+ compare rev-parse "subspace/one subspace/two other/three" "--namespace=heads/subspace --namespace=heads/other"
+
+'
+
+test_expect_success 'rev-parse --namespace=heads/someref master' '
+
+ compare rev-parse "master" "--namespace=heads/someref master"
+
+'
+
+test_expect_success 'rev-parse --namespace=heads' '
+
+ compare rev-parse "subspace/one subspace/two other/three subspace-x master someref" "--namespace=heads"
+
+'
+
+test_expect_success 'rev-list --namespace=refs/heads/subspace/' '
+
+ compare rev-list "subspace/one subspace/two" "--namespace=refs/heads/subspace/"
+
+'
+
+test_expect_success 'rev-list --namespace=refs/heads/subspace' '
+
+ compare rev-list "subspace/one subspace/two" "--namespace=refs/heads/subspace"
+
+'
+
+test_expect_success 'rev-list --namespace=heads/subspace' '
+
+ compare rev-list "subspace/one subspace/two" "--namespace=heads/subspace"
+
+'
+
+test_expect_success 'rev-list --namespace=heads/someref master' '
+
+ compare rev-parse "master" "--namespace=heads/someref master"
+
+'
+
+test_expect_success 'rev-list --namespace=heads/subspace --namespace=heads/other' '
+
+ compare rev-parse "subspace/one subspace/two other/three" "--namespace=heads/subspace --namespace=heads/other"
+
+'
+
+test_expect_success 'rev-list --namespace=heads' '
+
+ compare rev-parse "subspace/one subspace/two other/three subspace-x master someref" "--namespace=heads"
+
+'
+
+
+test_done
--
1.6.6.199.gff4b0
^ permalink raw reply related
* Re: ambiguous argument '...': unknown revision or path not in the working tree
From: Junio C Hamano @ 2010-01-18 6:59 UTC (permalink / raw)
To: Sven Joachim; +Cc: git
In-Reply-To: <87ska5vzbs.fsf@turtle.gmx.de>
Sven Joachim <svenjoac@gmx.de> writes:
> I've got a strange error message in 'git push':
>
> ,----
> | % git push
> | Counting objects: 47, done.
> | Delta compression using up to 2 threads.
> | Compressing objects: 100% (24/24), done.
> | Writing objects: 100% (24/24), 4.37 KiB, done.
> | Total 24 (delta 22), reused 0 (delta 0)
> | fatal: ambiguous argument '3bbc6def8a06e4411bee130b811ff9507e90503d:debian/changelog': unknown revision or path not in the working tree.
> | Use '--' to separate paths from revisions
I think there is nothing "fatal" in your git setup, other than that the
repository at the remote end you are pushing into has a broken hook script
(perhaps update, post-receive, or post-update) that is issuing the error
message. I would raise the issue with the owner of the repository if I
were you.
^ permalink raw reply
* Re: [PATCH] grep --no-index: allow use of "git grep" outside a git repository
From: Jeff King @ 2010-01-18 6:50 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, David Aguilar, Nanako Shiraishi
In-Reply-To: <7vk4vgsz5w.fsf@alter.siamese.dyndns.org>
On Sun, Jan 17, 2010 at 10:30:19PM -0800, Junio C Hamano wrote:
> Jeff King <peff@peff.net> writes:
>
> > Hmm. I like the new behavior. The implementation feels a little
> > hack-ish, like we should really be supporting full-on:
> >
> > git log --author=me --and --grep=foo
> >
> > That gets a little weird, though. We already have "--not" for ref
> > limiting, so clearly there is some conflict ...
>
> That is fundamentally wrong.
>
> Remember, "grep" works on two levels: a line matches or does not match the
> given set of patterns (rather, the expression given), and matched lines
> are shown. A file as a whole is considered to have matched if one or more
> lines produced a match, or under the --all-match option, only when all of
> the top-level ORed terms in the expression have fired for some lines in
> it.
Fundamentally wrong for the way "log --grep" is currently implemented
perhaps, but I don't see anything wrong with considering each commit as
a single "record", just as regular grep considers each line to be a
record. That is a much more useful distinction for log traversal than
lines, which are useless from the user's perspective. If searching for
two terms, I care about whether they are in the same commit message, but
I don't care at all about line breaks.
Yes, I know that internally --author is really about line-matching the
commit headers, but that is an implementation detail. The mental model
we should present to the user is record-matching based on specific
fields like author, committer, or body text.
-Peff
^ permalink raw reply
* Re: What's cooking in git.git (Jan 2010, #05; Sat, 16)
From: Junio C Hamano @ 2010-01-18 6:30 UTC (permalink / raw)
To: git; +Cc: Avery Pennarun
In-Reply-To: <7vljfxa1o6.fsf@alter.siamese.dyndns.org>
Junio C Hamano <gitster@pobox.com> writes:
> [Stalled]
>
> * ap/merge-backend-opts (2008-07-18) 6 commits
> - Document that merge strategies can now take their own options
> - Extend merge-subtree tests to test -Xsubtree=dir.
> - Make "subtree" part more orthogonal to the rest of merge-recursive.
> - Teach git-pull to pass -X<option> to git-merge
> - git merge -X<option>
> - git-merge-file --ours, --theirs
>
> "git pull" patch needs sq-then-eval fix to protect it from $IFS
> but otherwise seemed good.
Generally I hate to step in to fix up other's series, especially when I
know that the party responsible for issues is capable enough. As the
pre-release feature freeze gets closer, however, I sometimes find myself
doing so, and (I repeat) I hate it.
In any case, a squashable update to the "Teach git-pull to pass -X" would
look like this. Please give it extra set of eyeballs.
diff --git a/git-pull.sh b/git-pull.sh
index 6d961b6..fc3536b 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -68,10 +68,10 @@ do
1,-X)
usage ;;
*,-X)
- xx="-X $2"
+ xx="-X $(git rev-parse --sq-quote "$2")"
shift ;;
*,*)
- xx="$1" ;;
+ xx=$(git rev-parse --sq-quote "$1") ;;
esac
merge_args="$merge_args$xx "
;;
@@ -228,8 +228,15 @@ then
fi
merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
-test true = "$rebase" &&
- exec git-rebase $diffstat $strategy_args $merge_args --onto $merge_head \
- ${oldremoteref:-$merge_head}
-exec git-merge $diffstat $no_commit $squash $no_ff $ff_only $log_arg $strategy_args $merge_args \
- "$merge_name" HEAD $merge_head $verbosity
+case "$rebase" in
+true)
+ eval="git-rebase $diffstat $strategy_args $merge_args"
+ eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
+ ;;
+*)
+ eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only"
+ eval="$eval $log_arg $strategy_args $merge_args"
+ eval="$eval \"$merge_name\" HEAD $merge_head $verbosity"
+ ;;
+esac
+eval "exec $eval"
^ permalink raw reply related
* Re: [PATCH] grep --no-index: allow use of "git grep" outside a git repository
From: Junio C Hamano @ 2010-01-18 6:30 UTC (permalink / raw)
To: Jeff King; +Cc: git, David Aguilar, Nanako Shiraishi
In-Reply-To: <20100118055703.GA17879@coredump.intra.peff.net>
Jeff King <peff@peff.net> writes:
> Hmm. I like the new behavior. The implementation feels a little
> hack-ish, like we should really be supporting full-on:
>
> git log --author=me --and --grep=foo
>
> That gets a little weird, though. We already have "--not" for ref
> limiting, so clearly there is some conflict ...
That is fundamentally wrong.
Remember, "grep" works on two levels: a line matches or does not match the
given set of patterns (rather, the expression given), and matched lines
are shown. A file as a whole is considered to have matched if one or more
lines produced a match, or under the --all-match option, only when all of
the top-level ORed terms in the expression have fired for some lines in
it.
And --not and --and are both elements of grep expression that determines
if the expression matches "a single line". --author=me --and --grep=foo
would ask: does the "^author " line in the header have "me" _and_ also
string "foo" on it at the same time?
IOW, most of the "logical" stuff (including the precedence binding
parentheses) works at a line level. --all-match is currently the only
thing that affects "grep -l" (and "will the commit get shown") behaviour
by collecting hits from the whole buffer.
^ permalink raw reply
* Re: [PATCH] grep --no-index: allow use of "git grep" outside a git repository
From: Jeff King @ 2010-01-18 5:57 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, David Aguilar, Nanako Shiraishi
In-Reply-To: <7v3a24ukku.fsf@alter.siamese.dyndns.org>
On Sun, Jan 17, 2010 at 08:02:25PM -0800, Junio C Hamano wrote:
> Subject: "log --author=me --grep=it" should find intersection, not union
>
> Historically, any grep filter in "git log" family of commands were taken
> as restricting to commits with any of the words in the commit log message.
> However, the user almost always want to find commits "done by this person
> on that topic". With "--all-match" option, a series of grep patterns can
> be turned into a requirement that all of them must produce a match, but
> that makes it impossible to ask for "done by me, on either this or that"
> with:
>
> log --author=me --grep=this --grep=that
>
> because it will require both "this" and "that" to appear.
>
> Change the "header" parser of grep library to treat the headers specially.
> When parsing the above, behave as if it was specified like this on the
> command line:
>
> --all-match --author=me '(' --grep=this --grep=that ')'
>
> Even though the "log" command line parser doesn't give direct access to
> the extended grep syntax to group terms with parentheses, this change will
> cover the majority of the case the users would want.
Hmm. I like the new behavior. The implementation feels a little
hack-ish, like we should really be supporting full-on:
git log --author=me --and --grep=foo
That gets a little weird, though. We already have "--not" for ref
limiting, so clearly there is some conflict over exactly what logical
operators would be operating on. I guess we could use context to see
that the adjacent arguments were grep-related.
So perhaps, as you say, this is enough as it covers the usual case.
-Peff
^ permalink raw reply
* idea: git "came from" tags
From: D Herring @ 2010-01-18 4:22 UTC (permalink / raw)
To: git
Actors:
- public "upstream" repository
- public "local" repository
- end users tracking both
Situation:
- local starts by tracking upstream
- local makes changes, commits, and sends upstream
- users now tracking local ahead of upstream
- upstream makes modified commits
- local satisfied, wants to reset master to upstream/master
Problem:
- A merge will perpetually leave two parallel branches. Even though
there are no longer any diffs, local/master cannot use the same
objects as upstream/master.
- A hard reset lets local/master return to sharing objects with
upstream/master; but this may break pulls or cause other problems for
users.
Proposed solution:
- Local adds a "came from" tag to upstream/master, leaves a tag on the
head of local/master, and does a hard reset from local/master to
upstream/master. When a user tracking local/master does a pull, their
client detects a non-fast-forward, finds the came-from tag, and treats
it as a fast-forward.
Basically, this is a protocol to glue a "strategy ours" merge onto an
existing tree. This way local can cleanly track upstream, with no
added complexity in the nominal (no local changes) case.
Example:
Without this addition, local/master looks something like
u1 - u2 - u3 - u4 - u5 - u6 ...
\- l1 - l2\+ m1 - \+ m2\+ m3 ...
With this addition, local/master looks like
u1 - u2 - u3(tcf) - u4 - u5 - u6 ...
\- l1 - l2 - t0
where
* u# = upstream changes
* l# = local changes
* m# = local merges (m1=u3, m2=u5, m3=u6, ...)
* the tcf tag points to t0, and t0 tags the end of the local mods
Pseudo-shell-code addition to git-pull:
fetch local/master
fast_forward=usual test whether local/master contains user/master
if test $fast_forward = no
then
for tag in $fetched_tags # something like this or git-describe
do
if is_came_from($tag) && (came_from($tag) contains user/master)
then
fast_forward=yes
break
fi
done
fi
Comments? I think this is completely implementable (though I'm not
well-versed in git internals). Since it only triggers during
non-fast-forward operations, it should have negligible performance impact.
Of course, it would be even better if somebody shows me how to do this
with the current tools. Maybe I'm just doing it all wrong.
Thanks,
Daniel
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox