* [PATCH] branch.autosetupmerge: allow boolean values, or "all"
From: Johannes Schindelin @ 2007-07-08 12:41 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Paolo Bonzini
In-Reply-To: <7vhcof2rur.fsf@assigned-by-dhcp.cox.net>
Junio noticed that switching on autosetupmerge unilaterally started
cluttering the config for local branches. That is not the original
intention of branch.autosetupmerge, which was meant purely for
convenience when branching off of remote branches, but that semantics
got lost somewhere.
If you still want that "new" behavior, you can switch
branch.autosetupmerge to the value "all". Otherwise, it is interpreted
as a boolean, which triggers setting up defaults _only_ when branching
off of a remote branch, i.e. the originally intended behavior.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
On Sun, 8 Jul 2007, Junio C Hamano wrote:
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> > "git branch --track" will setup config variables when
> > branching from a remote branch, so that if you say "git pull"
> > while being on that branch, it automatically fetches the
> > correct remote, and merges the correct branch.
>
> While I think it would have been the right thing to do if the
> code did this only for a remote branch, I think there is a bug
> somewhere. I just saw this:
>
> ... some random changes ...
> master$ git commit -a -s -m 'Some work meant for topic.'
> master$ git branch jc/new-topic
> Branch jc/new-topic set up to track local branch refs/heads/master
>
> Eh? I did not want this to get applied for my local branches.
That is certainly unexpected and unwelcomed. Alas, I think it is
one of the consequences of rarely executed (and thus, tested)
code.
I rarely branch, but use one long running branch to commit and
revert, so that a "git log -S<keyword> -p" brings me back my huge
debug output changes, and therefore I did not catch it.
> I do not necessarily think the command line --track is broken.
Me, neither. Therefore, this patch does not change the semantics
of that one. But it was really unexpected for me to see that this
works with anything but remote branches.
> I am very tempted to revert this, but won't do so tonight, yet.
Well, I marked it as RFC, and was surprised to wake up to it being
applied. But thanks for not reverting right away; I think this
patch should fix the issue.
builtin-branch.c | 18 ++++++++++++------
t/t3200-branch.sh | 9 +++++++++
2 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/builtin-branch.c b/builtin-branch.c
index 507b47c..49195a1 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -22,7 +22,7 @@ static const char builtin_branch_usage[] =
static const char *head;
static unsigned char head_sha1[20];
-static int branch_track_remotes = 1;
+static int branch_track = 1; /* 0 = none, 1 = remotes, 2 = all */
static int branch_use_color;
static char branch_colors[][COLOR_MAXLEN] = {
@@ -66,8 +66,12 @@ static int git_branch_config(const char *var, const char *value)
color_parse(value, var, branch_colors[slot]);
return 0;
}
- if (!strcmp(var, "branch.autosetupmerge"))
- branch_track_remotes = git_config_bool(var, value);
+ if (!strcmp(var, "branch.autosetupmerge")) {
+ if (!strcmp(value, "all"))
+ branch_track = 2;
+ else
+ branch_track = git_config_bool(var, value);
+ }
return git_default_config(var, value);
}
@@ -525,7 +529,9 @@ static void create_branch(const char *name, const char *start_name,
/* When branching off a remote branch, set up so that git-pull
automatically merges from there. So far, this is only done for
remotes registered via .git/config. */
- if (real_ref && track)
+ if (real_ref && (track == 2 ||
+ (track == 1 &&
+ !prefixcmp(real_ref, "refs/remotes/"))))
set_branch_defaults(name, real_ref);
if (write_ref_sha1(lock, sha1, msg) < 0)
@@ -586,7 +592,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
int i;
git_config(git_branch_config);
- track = branch_track_remotes;
+ track = branch_track;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
@@ -598,7 +604,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
break;
}
if (!strcmp(arg, "--track")) {
- track = 1;
+ track = 2;
continue;
}
if (!strcmp(arg, "--no-track")) {
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index c6f472a..a19e961 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -148,6 +148,15 @@ test_expect_success 'test tracking setup via config' \
test $(git config branch.my3.remote) = local &&
test $(git config branch.my3.merge) = refs/heads/master'
+test_expect_success 'autosetupmerge = all' '
+ git config branch.autosetupmerge true &&
+ git branch all1 master &&
+ test -z "$(git config branch.all1.merge)" &&
+ git config branch.autosetupmerge all &&
+ git branch all2 master &&
+ test $(git config branch.all2.merge) = refs/heads/master
+'
+
test_expect_success 'test overriding tracking setup via --no-track' \
'git config branch.autosetupmerge true &&
git config remote.local.url . &&
--
1.5.3.rc0.2742.g2050
^ permalink raw reply related
* Re: [PATCH] Fix "apply --reverse" with regard to whitespace
From: Johannes Schindelin @ 2007-07-08 12:47 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Daniel Barkalow, git
In-Reply-To: <7vd4z32rqc.fsf@assigned-by-dhcp.cox.net>
Hi,
On Sun, 8 Jul 2007, Junio C Hamano wrote:
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> > "git apply" used to take check the whitespace in the wrong
> > direction.
> >
> > Noticed by Daniel Barkalow.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >
> > On Fri, 6 Jul 2007, Daniel Barkalow wrote:
> >
> > > If you apply in reverse a patch which adds junk (e.g., terminal
> > > whitespace), it complains about the junk you're adding, even
> > > though (since it's in reverse) you're actually removing that
> > > junk.
> >
> > This fixes it.
>
> Hmm. Does this cover the "trailing blank lines removal" as
> well?
-- snip --
diff --git a/TODO b/TODO
index ecd2b85..8935376 100644
--- a/TODO
+++ b/TODO
@@ -1,6 +1,5 @@
+interdiff between commits
diff with file/symlink
-remote remove <nick>
-libification: setjmp() error handler with xmalloc(), xcalloc(), xfree() handling
fix diff_filepair() mess in diff.c
merge -s rebase
build in ls-remote
diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh
index 9ae2b3a..f3f9181 100755
--- a/t/t4116-apply-reverse.sh
+++ b/t/t4116-apply-reverse.sh
@@ -84,6 +84,7 @@ test_expect_success 'apply in reverse without postimage' '
test_expect_success 'reversing a whitespace introduction' '
sed "s/a/a /" < file1 > file1.new &&
+ echo >> file1.new &&
mv file1.new file1 &&
git diff | git apply --reverse --whitespace=error
'
-- snap --
Apparently.
Or not. Since a patch adding empty lines does not trigger any error with
--whitespace=error. I just tested that.
Ciao,
Dscho
^ permalink raw reply related
* Re: [PATCH] merge-tree: sometimes, d/f conflict is not an issue
From: Johannes Schindelin @ 2007-07-08 12:53 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Gerrit Pape, git, Rémi Vanicat
In-Reply-To: <7v644v5tr3.fsf@assigned-by-dhcp.cox.net>
Hi,
On Sat, 7 Jul 2007, Junio C Hamano wrote:
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> >> IOW, don't make unpack-trees to make policy decisions on final
> >> resolution, unless it is operating under aggressive rule (where the
> >> caller explicitly allows it to make more than the "trivial" decisions).
> >> The caller (in this case, merge-recursive) should see A at stage #2 with
> >> A/B at stages #1 and #3 and decide what to do.
> >
> > Okay, so you're saying that merge-recursive should use the aggressive
> > strategy?
>
> I do not think so.
Yes, I realized that by running the tests with it. A rename A->B in one,
and A->C in the other branch will go undetected.
I should have written this into my mail posting the WIP patch, but
frankly, I was too tired.
Ciao,
Dscho
^ permalink raw reply
* Re: [PATCH] merge-tree: sometimes, d/f conflict is not an issue
From: Johannes Schindelin @ 2007-07-08 13:16 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Gerrit Pape, git, Rémi Vanicat
In-Reply-To: <7vwsxb4e2q.fsf@assigned-by-dhcp.cox.net>
Hi,
On Sat, 7 Jul 2007, Junio C Hamano wrote:
> Junio C Hamano <gitster@pobox.com> writes:
>
> > The last time I looked at merge-recursive's D/F check, I found that it
> > was not quite doing things right. I may be able to dig up what I
> > posted to the list...
>
> It was from around April 7th-10th this year.
Unfortunately, this is way over my time budget. As well as over my
intelligence budget, since I did not even succeed in understanding the
code in threeway_merge _at all_.
Besides, IMHO there is a deeper issue. Since merge-recursive started out
as a Python script, and grew there until it was usable, and grew the
rename detection therein, too, until it was finally converted to C, it
accumulated a lot of features that would have been nice to have
independently.
Almost the same goes for unpack-trees, which (its name to the contrary)
does quite a few things to merge entries, too. And it tries to detect d/f
conflicts, too.
So there we are, with two really big and unwieldy chunks of code, each
deserving an own GSoC project to clean them up. Or maybe not even a GSoC
project, but a longer project.
What I would _like_ to see is something as clean as merge-tree. Which is
clearly separated (code and file wise, too) into these stages:
- reading the trees
- determining renames
- determining true d/f conflicts
- threeway merge
- writing the tree object
- writing the work tree
- recursive
Ideally, merge-recursive would really have been as simple as
case "$1" in
--index_only)
index_only=$1
shift
esac
a="$1"
b="$2"
set $(git merge-base --all $a $b)
temp=$1
shift
while case $# in 0) break;; esac
do
temp=$(git merge-recursive --index-only $temp $1)
shift
done
git merge-non-recursive $index_only $temp -- "$a" "$b"
because _read-tree -m_ should have learnt about renames, _not_
merge-recursive.
As it is, both unpack_trees() and merge-recursive have a certain degree of
not-quite duplicated yet wants-to-do-largely-the-same functionality.
Which of course leads to much finger pointing: "it's unpack_trees() fault.
no. it's merge-recursive's fault. no, vice versa."
Maybe the proper way out is really to start from merge-tree.c and do
something which is easy to understand, and concise, and thus has a much
lesser chance of being buggy.
Ciao,
Dscho
^ permalink raw reply
* Re: [Qgit RFC] commit --amend
From: Jan Hudec @ 2007-07-08 13:38 UTC (permalink / raw)
To: Marco Costalba; +Cc: git
In-Reply-To: <e5bfff550707060112p29b9565bw9ccba6601745b850@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1514 bytes --]
On Fri, Jul 06, 2007 at 10:12:50 +0200, Marco Costalba wrote:
> On 7/5/07, Jan Hudec <bulb@ucw.cz> wrote:
>> Therefore I'll either have to always ask git via run("git-rev-parse HEAD",
>> head), add HEAD into the map, or store HEAD somewhere in the Git object.
>> Which do you think makes most sense?
>
> Asking git when you need it and keep HEAD value only for the minimum
> time required to execute the commit command.
>
> - HEAD is very 'volatile'
>
> - commit is _not_ performance critical.
>
> - commit, being a write operation, is instead bugs/misbehaviour
> critical (a big point to use an high level "git-commit" BTW)
>
> - asking git with getRefSha("HEAD", ANY_REF, true) is very quick and
> in any case much quicker then the whole commit dance.
Yes. It is also much faster to write in code, but...
> - someone can always change the repo behind you, qgit is not the only
> interface to git ;-) does exist also the command line.
And the commit in qgit should better fail loudly if that happens, because the
list of files or something else might no longer make sense.
There is actually just one thing I need the head for -- passing it as 3rd
argument to git-update-ref. That should be done purely as safety measure --
if the value does not match, the command will fail. And for that safety
measure to be useful, I need value of the head at the time user openend the
commit dialog, NOT the time user clicked on commit button.
--
Jan 'Bulb' Hudec <bulb@ucw.cz>
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]
^ permalink raw reply
* Re: [Qgit RFC] commit --amend
From: Marco Costalba @ 2007-07-08 13:49 UTC (permalink / raw)
To: Jan Hudec; +Cc: git
In-Reply-To: <20070708133825.GE3991@efreet.light.src>
On 7/8/07, Jan Hudec <bulb@ucw.cz> wrote:
> On Fri, Jul 06, 2007 at 10:12:50 +0200, Marco Costalba wrote:
> > On 7/5/07, Jan Hudec <bulb@ucw.cz> wrote:
> >> Therefore I'll either have to always ask git via run("git-rev-parse HEAD",
> >> head), add HEAD into the map, or store HEAD somewhere in the Git object.
> >> Which do you think makes most sense?
> >
> > Asking git when you need it and keep HEAD value only for the minimum
> > time required to execute the commit command.
> >
> > - HEAD is very 'volatile'
> >
> > - commit is _not_ performance critical.
> >
> > - commit, being a write operation, is instead bugs/misbehaviour
> > critical (a big point to use an high level "git-commit" BTW)
> >
> > - asking git with getRefSha("HEAD", ANY_REF, true) is very quick and
> > in any case much quicker then the whole commit dance.
>
> Yes. It is also much faster to write in code, but...
>
> > - someone can always change the repo behind you, qgit is not the only
> > interface to git ;-) does exist also the command line.
>
> And the commit in qgit should better fail loudly if that happens, because the
> list of files or something else might no longer make sense.
>
> There is actually just one thing I need the head for -- passing it as 3rd
> argument to git-update-ref. That should be done purely as safety measure --
> if the value does not match, the command will fail. And for that safety
> measure to be useful, I need value of the head at the time user openend the
> commit dialog, NOT the time user clicked on commit button.
>
It it has "commit dialog" life span I would suggets to save in a
"commit dialog" object member data.
Marco
^ permalink raw reply
* Re: git describe origin ?
From: Francis Moreau @ 2007-07-08 13:51 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: git
In-Reply-To: <Pine.LNX.4.64.0707071728330.4093@racer.site>
On 7/7/07, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> On Sat, 7 Jul 2007, Francis Moreau wrote:
>
> > I was wondering what does 'git describe origin' command mean on a git
> > repo. Does it mean ?
> >
> > a/ git describe origin/HEAD
> > b/ git describe origin/master
> > c/ something else
>
> This is completely unrelated to "git describe". It is about naming
> commits AKA "specifying revisions". You might find the section "SPECIFYING
> REVISIONS" in Documentation/git-rev-parse.txt especially helpful. FWIW
> this section is hinted at in the section "Symbolic Identifiers" in
> Documentation/git.txt.
>
> If you're too lazy to read, it's a/.
>
Ok I took a look to it and I think I get the idea. But now I don't
understand this example taken from "git-branch" documentation:
"""
Delete unneeded branch
$ git clone git://git.kernel.org/.../git.git my.git
$ cd my.git
$ git branch -d -r todo html man (1)
"""
I think the last command can't work since the given refs can't be
found according the rules given by the documention you mentioned
earlier.
thanks
--
Francis
^ permalink raw reply
* Re: [PATCH v3] revision: allow selection of commits that do not match a pattern
From: Johannes Schindelin @ 2007-07-08 14:22 UTC (permalink / raw)
To: skimo; +Cc: Junio C Hamano, git
In-Reply-To: <20070708105719.GH1528MdfPADPa@greensroom.kotnet.org>
Hi,
just to give you an impression of what I had in mind, here is a WIP. It
is not completely thought through, for example I did not make up my mind
how to handle something like "--not --not-at-all <pattern>". Oh, and the
code for non-status_only is not there. And builtin-grep does not see any
of this, yet. But you'll get the idea:
---
grep.c | 19 +++++++++++++++----
grep.h | 3 +++
revision.c | 13 ++++++++++++-
3 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/grep.c b/grep.c
index f67d671..40a2620 100644
--- a/grep.c
+++ b/grep.c
@@ -66,20 +66,24 @@ static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
static struct grep_expr *compile_pattern_not(struct grep_pat **list)
{
+ const char *what;
struct grep_pat *p;
struct grep_expr *x;
p = *list;
switch (p->token) {
case GREP_NOT:
+ case GREP_NOT_AT_ALL:
+ what = p->token == GREP_NOT ? "--not" : "--not-at-all";
if (!p->next)
- die("--not not followed by pattern expression");
+ die("%s not followed by pattern expression", what);
*list = p->next;
x = xcalloc(1, sizeof (struct grep_expr));
- x->node = GREP_NODE_NOT;
+ x->node = p->token == GREP_NOT ?
+ GREP_NODE_NOT : GREP_NODE_NOT_AT_ALL;
x->u.unary = compile_pattern_not(list);
if (!x->u.unary)
- die("--not followed by non pattern expression");
+ die("%s followed by non pattern expression", what);
return x;
default:
return compile_pattern_atom(list);
@@ -173,6 +177,7 @@ static void free_pattern_expr(struct grep_expr *x)
case GREP_NODE_ATOM:
break;
case GREP_NODE_NOT:
+ case GREP_NODE_NOT_AT_ALL:
free_pattern_expr(x->u.unary);
break;
case GREP_NODE_AND:
@@ -316,6 +321,10 @@ static int match_expr_eval(struct grep_opt *o,
case GREP_NODE_NOT:
h = !match_expr_eval(o, x->u.unary, bol, eol, ctx, 0);
break;
+ case GREP_NODE_NOT_AT_ALL:
+ if (match_expr_eval(o, x->u.unary, bol, eol, ctx, 0))
+ o->not_at_all = 1;
+ break;
case GREP_NODE_AND:
if (!collect_hits)
return (match_expr_eval(o, x->u.binary.left,
@@ -382,6 +391,8 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
unsigned count = 0;
enum grep_context ctx = GREP_CONTEXT_HEAD;
+ opt->not_at_all = 0;
+
if (buffer_is_binary(buf, size)) {
switch (opt->binary) {
case GREP_BINARY_DEFAULT:
@@ -500,7 +511,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
return 0;
if (opt->status_only)
- return 0;
+ return !opt->not_at_all;
if (opt->unmatch_name_only) {
/* We did not see any hit, so we want to show this */
printf("%s\n", name);
diff --git a/grep.h b/grep.h
index d252dd2..f80a1c2 100644
--- a/grep.h
+++ b/grep.h
@@ -10,6 +10,7 @@ enum grep_pat_token {
GREP_CLOSE_PAREN,
GREP_NOT,
GREP_OR,
+ GREP_NOT_AT_ALL,
};
enum grep_context {
@@ -31,6 +32,7 @@ enum grep_expr_node {
GREP_NODE_NOT,
GREP_NODE_AND,
GREP_NODE_OR,
+ GREP_NODE_NOT_AT_ALL,
};
struct grep_expr {
@@ -68,6 +70,7 @@ struct grep_opt {
unsigned extended:1;
unsigned relative:1;
unsigned pathname:1;
+ unsigned not_at_all:1; /* is set if the pattern was seen */
int regflags;
unsigned pre_context;
unsigned post_context;
diff --git a/revision.c b/revision.c
index 5184716..3df8a57 100644
--- a/revision.c
+++ b/revision.c
@@ -823,6 +823,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)
{
+ int negate = *ptn == '!';
if (!revs->grep_filter) {
struct grep_opt *opt = xcalloc(1, sizeof(*opt));
opt->status_only = 1;
@@ -830,6 +831,13 @@ static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token
opt->regflags = REG_NEWLINE;
revs->grep_filter = opt;
}
+ if (negate) {
+ revs->grep_filter->extended = 1;
+ append_grep_pattern(revs->grep_filter, ptn,
+ "command line", 0, GREP_NOT_AT_ALL);
+ }
+ if (negate || ( *ptn == '\\' && ptn[1] == '!'))
+ ptn++;
append_grep_pattern(revs->grep_filter, ptn,
"command line", 0, what);
}
@@ -839,7 +847,10 @@ static void add_header_grep(struct rev_info *revs, const char *field, const char
char *pat;
const char *prefix;
int patlen, fldlen;
+ int negate = *pattern == '!';
+ if (negate || (*pattern == '\\' && pattern[1] == '!'))
+ pattern++;
fldlen = strlen(field);
patlen = strlen(pattern);
pat = xmalloc(patlen + fldlen + 10);
@@ -848,7 +859,7 @@ static void add_header_grep(struct rev_info *revs, const char *field, const char
prefix = "";
pattern++;
}
- sprintf(pat, "^%s %s%s", field, prefix, pattern);
+ sprintf(pat, "%s^%s %s%s", negate ? "!" : "", field, prefix, pattern);
add_grep(revs, pat, GREP_PATTERN_HEAD);
}
^ permalink raw reply related
* [PATCH] Fix "git log --parent -m" from emitting duplicated parent info
From: Marco Costalba @ 2007-07-08 14:37 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Git Mailing List
Without this patch in case of a merge, duplicated parents are
omitted in first line output, but still listed in following
parents information.
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
---
Following the hint of someone I submit this patch.
Please take it as a wish of not reverting the patch ;-)
log-tree.c | 25 +++++++++++++++++--------
revision.h | 1 +
2 files changed, 18 insertions(+), 8 deletions(-)
diff --git a/log-tree.c b/log-tree.c
index 24aea6b..e5d40fe 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -6,11 +6,11 @@
struct decoration name_decoration = { "object names" };
-static void clear_tmp_mark(struct commit_list *p)
+static void clear_flag(struct commit_list *p, unsigned int flag)
{
while (p) {
struct commit *c = p->item;
- c->object.flags &= ~TMP_MARK;
+ c->object.flags &= ~flag;
p = p->next;
}
}
@@ -23,7 +23,7 @@ static void show_parents(
* be used locally, but the user should clean
* things up after it is done with them.
*/
- clear_tmp_mark(commit->parents);
+ clear_flag(commit->parents, TMP_MARK);
for (p = commit->parents; p ; p = p->next) {
struct commit *parent = p->item;
if (parent->object.flags & TMP_MARK)
@@ -31,7 +31,7 @@ static void show_parents(struct
printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
parent->object.flags |= TMP_MARK;
}
- clear_tmp_mark(commit->parents);
+ clear_flag(commit->parents, TMP_MARK);
}
static void show_decorations(struct commit *commit)
@@ -391,16 +391,24 @@ static int log_tree_diff(
/* If we show individual diffs, show the parent info */
log->parent = parents->item;
}
-
showed_log = 0;
+ clear_flag(parents, TMP_MARK_2);
+
for (;;) {
struct commit *parent = parents->item;
- diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
- log_tree_diff_flush(opt);
+ /* TMP_MARK_2 is a general purpose flag that can
+ * be used locally nested with TMP_MARK, but the user
+ * should clean things up after it is done with them.
+ */
+ if (!opt->parents || !(parent->object.flags & TMP_MARK_2)) {
- showed_log |= !opt->loginfo;
+ diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
+ log_tree_diff_flush(opt); // will use TMP_MARK
+ showed_log |= !opt->loginfo;
+ parent->object.flags |= TMP_MARK_2;
+ }
/* Set up the log info for the next parent, if any.. */
parents = parents->next;
if (!parents)
@@ -408,6 +416,7 @@ static int log_tree_diff(
log->parent = parents->item;
opt->loginfo = log;
}
+ clear_flag(parents, TMP_MARK_2);
return showed_log;
}
diff --git a/revision.h b/revision.h
index f46b4d5..403507f 100644
--- a/revision.h
+++ b/revision.h
@@ -10,6 +10,7 @@
#define CHILD_SHOWN (1u<<6)
#define ADDED (1u<<7) /* Parents already parsed and added? */
#define SYMMETRIC_LEFT (1u<<8)
+#define TMP_MARK_2 (1u<<9) /* for isolated cases; clean after use */
struct rev_info;
struct log_info;
--
1.5.3.rc0.65.g39a4d-dirty
^ permalink raw reply related
* Re: git cvsimport branches not consistent with CVS branches
From: Steffen Prohaska @ 2007-07-08 14:38 UTC (permalink / raw)
To: Robin Rosenberg, Brian Downing, Gordon Heydon; +Cc: Git Mailing List
In-Reply-To: <200707081253.06129.robin.rosenberg.lists@dewire.com>
On Jul 8, 2007, at 12:53 PM, Robin Rosenberg wrote:
> Corecode's fromcvs is pretty fast and incremental and AFAIK
> accurate. I had
> plenty problems with cvsimport, but fromcvs keeps in sync with the
> CVS repo.
> Get it at http://ww2.fs.ei.tum.de/~corecode/hg/fromcvs/ .
>
> It does not convert regular tags, only branches, however so there
> is something to
> do for those that want a complete cvs import.
Did anyone compare
* git-cvsimport with cvsps patches from [1]
* parsecvs [2]
* fromcvs
and can give a recommendation?
My experience with plain git-cvsimport (without cvsps patches from [1])
is that it has a lot of problems. I'd recommend not to use it for
incremental import and be very suspicious about the git repository
created by git-cvsimport. You need to carefully validate the repository.
It's likely that you need to fix imported branches. The trunk seems to
be ok.
Steffen
[1] http://ydirson.free.fr/en/software/scm/cvsps.html
[2] http://anongit.freedesktop.org/git/users/keithp/repos/parsecvs.git/
^ permalink raw reply
* Re: [PATCH v3] revision: allow selection of commits that do not match a pattern
From: Sven Verdoolaege @ 2007-07-08 14:57 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Junio C Hamano, git
In-Reply-To: <Pine.LNX.4.64.0707081519230.4248@racer.site>
On Sun, Jul 08, 2007 at 03:22:06PM +0100, Johannes Schindelin wrote:
> @@ -382,6 +391,8 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
> unsigned count = 0;
> enum grep_context ctx = GREP_CONTEXT_HEAD;
>
> + opt->not_at_all = 0;
> +
> if (buffer_is_binary(buf, size)) {
> switch (opt->binary) {
> case GREP_BINARY_DEFAULT:
> @@ -500,7 +511,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
> return 0;
>
> if (opt->status_only)
> - return 0;
> + return !opt->not_at_all;
> if (opt->unmatch_name_only) {
> /* We did not see any hit, so we want to show this */
> printf("%s\n", name);
I don't understand this part.
Aren't you changing the return value from 0 to 1 here if there is no NOT_AT_ALL node?
> @@ -68,6 +70,7 @@ struct grep_opt {
> unsigned extended:1;
> unsigned relative:1;
> unsigned pathname:1;
> + unsigned not_at_all:1; /* is set if the pattern was seen */
> int regflags;
> unsigned pre_context;
> unsigned post_context;
The name for this field is also a bit confusing.
Wouldn't "matched_some_line" or some such by more appropriate?
skimo
^ permalink raw reply
* Re: [PATCH] git-commit: don't add multiple Signed-off-by: from the same identity
From: Gerrit Pape @ 2007-07-08 15:00 UTC (permalink / raw)
To: Junio C Hamano, git
In-Reply-To: <7vy7hte717.fsf@assigned-by-dhcp.cox.net>
On Fri, Jul 06, 2007 at 11:11:48AM -0700, Junio C Hamano wrote:
> If you are trying to avoid a run of Signed-off-by: lines like this:
>
> Signed-off-by: Original Author <oa@example.com>
> Signed-off-by: First Reviewer <fr@example.com>
> Signed-off-by: Second Reviewer <sr@example.com>
> Signed-off-by: Original Author <oa@example.com>
> Signed-off-by: Subsystem Integrator <si@example.com>
>
> It is not a bug. If the last signed-off-by is not from
> yourself, your signed-off-by is added when you ask with "-s",
This is what the patch does, it only checks against the final line,
sorry, the subject is incorrect. The behavior currently is
$ ./git-commit -m foo foo
Created commit 2fad03e: foo
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 foo
$ EDITOR=cat ./git-commit --amend -s foo |head -n4
foo
Signed-off-by: Gerrit Pape <pape@smarden.org>
$ EDITOR=cat ./git-commit --amend -s foo |head -n4
foo
Signed-off-by: Gerrit Pape <pape@smarden.org>
Signed-off-by: Gerrit Pape <pape@smarden.org>
$
with the patch, iff the last line already was the signoff to be added,
it won't be added again.
> We have deliberately excluded what your other patch tries to do
> for a reason. Even though these lines are not digitally signed,
> the intent of adding a Signed-off-by: line with your name is
> that you are certifying its origin, according to the definition
> of DCO (see Documentation/SubmittingPatches). This should be a
> conscious act from the signer's part, and making it automatic
> with a config variable that you set once and forget makes it
> much less meaningful.
Okay, to me personally it would be convenient, for the git repository I
have no problem with, and want to simply singoff all commits, for other
repos none.
Regards, Gerrit.
^ permalink raw reply
* [PATCH 2/4] export add_ref_decoration
From: skimo @ 2007-07-08 16:23 UTC (permalink / raw)
To: git, Junio C Hamano
In-Reply-To: <11839118073186-git-send-email-skimo@liacs.nl>
From: Sven Verdoolaege <skimo@kotnet.org>
add_ref_decoration is also useful outside of git-log.
Since the name_decoration declaration appears in commit.h,
the function is moved to commit.c.
Signed-off-by: Sven Verdoolaege <skimo@kotnet.org>
---
builtin-log.c | 25 -------------------------
commit.c | 27 +++++++++++++++++++++++++++
commit.h | 3 +++
log-tree.c | 2 --
4 files changed, 30 insertions(+), 27 deletions(-)
diff --git a/builtin-log.c b/builtin-log.c
index 13bae31..c14eea5 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -21,31 +21,6 @@ static const char *fmt_patch_subject_prefix = "PATCH";
/* this is in builtin-diff.c */
void add_head(struct rev_info *revs);
-static void add_name_decoration(const char *prefix, const char *name, struct object *obj)
-{
- int plen = strlen(prefix);
- int nlen = strlen(name);
- struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + plen + nlen);
- memcpy(res->name, prefix, plen);
- memcpy(res->name + plen, name, nlen + 1);
- res->next = add_decoration(&name_decoration, obj, res);
-}
-
-static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
-{
- struct object *obj = parse_object(sha1);
- if (!obj)
- return 0;
- add_name_decoration("", refname, obj);
- while (obj->type == OBJ_TAG) {
- obj = ((struct tag *)obj)->tagged;
- if (!obj)
- break;
- add_name_decoration("tag: ", refname, obj);
- }
- return 0;
-}
-
static void cmd_log_init(int argc, const char **argv, const char *prefix,
struct rev_info *rev)
{
diff --git a/commit.c b/commit.c
index 03436b1..24d7dd4 100644
--- a/commit.c
+++ b/commit.c
@@ -1553,3 +1553,30 @@ int in_merge_bases(struct commit *commit, struct commit **reference, int num)
free_commit_list(bases);
return ret;
}
+
+struct decoration name_decoration = { "object names" };
+
+static void add_name_decoration(const char *prefix, const char *name, struct object *obj)
+{
+ int plen = strlen(prefix);
+ int nlen = strlen(name);
+ struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + plen + nlen);
+ memcpy(res->name, prefix, plen);
+ memcpy(res->name + plen, name, nlen + 1);
+ res->next = add_decoration(&name_decoration, obj, res);
+}
+
+int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+{
+ struct object *obj = parse_object(sha1);
+ if (!obj)
+ return 0;
+ add_name_decoration("", refname, obj);
+ while (obj->type == OBJ_TAG) {
+ obj = ((struct tag *)obj)->tagged;
+ if (!obj)
+ break;
+ add_name_decoration("tag: ", refname, obj);
+ }
+ return 0;
+}
diff --git a/commit.h b/commit.h
index 467872e..bf23535 100644
--- a/commit.h
+++ b/commit.h
@@ -122,4 +122,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
int depth, int shallow_flag, int not_shallow_flag);
int in_merge_bases(struct commit *, struct commit **, int);
+
+extern int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
+
#endif /* COMMIT_H */
diff --git a/log-tree.c b/log-tree.c
index 8624d5a..b69f029 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -4,8 +4,6 @@
#include "log-tree.h"
#include "reflog-walk.h"
-struct decoration name_decoration = { "object names" };
-
static void show_parents(struct commit *commit, int abbrev)
{
struct commit_list *p;
--
1.5.3.rc0.68.geec71-dirty
^ permalink raw reply related
* [PATCH 0/4] Add git-rewrite-commits
From: skimo @ 2007-07-08 16:23 UTC (permalink / raw)
To: git, Junio C Hamano
From: Sven Verdoolaege <skimo@kotnet.org>
This patch series adds git-rewrite-commits
and depends (for the tests in the fourth part)
on my earlier patch to allow negative matches
(or an extended patch by Johannes).
[PATCH 1/4] export get_short_sha1
[PATCH 2/4] export add_ref_decoration
[PATCH 3/4] revision: mark commits that didn't match a pattern for later use
[PATCH 4/4] Add git-rewrite-commits
The first two should be fairly uncontroversial.
The third may be considered a waste of a precious bit.
If so, any suggestions for other ways of passing on this information
are welcomed.
The fourth contains the actual git-rewrite-commits builtin.
My main motivation was that cg-admin-rewritehist doesn't
change the SHA1's in commit message and I don't like shell
programming.
skimo
^ permalink raw reply
* [PATCH 4/4] Add git-rewrite-commits
From: skimo @ 2007-07-08 16:23 UTC (permalink / raw)
To: git, Junio C Hamano
In-Reply-To: <11839118073186-git-send-email-skimo@liacs.nl>
From: Sven Verdoolaege <skimo@kotnet.org>
This builtin is similar to git-filter-branch (and cg-admin-rewritehist).
The main difference is that git-rewrite-commits will automatically
rewrite any SHA1 in a commit message to the rewritten SHA1 as
well as any reference (in .git/refs/) that points to a rewritten commit.
It's also a lot faster than git-filter-branch if no external command
is called. For example, running either to eliminating a commit specified
as a graft results in the following timings, both being performed
on a freshly cloned copy of a small repo:
bash-3.00$ time git-filter-branch test
Rewrite 274fe3dfb8e8c7d0a6ce05138bdb650de7b459ea (425/425)
Rewritten history saved to the test branch
real 0m30.845s
user 0m13.400s
sys 0m19.640s
bash-3.00$ time git-rewrite-commits
real 0m0.223s
user 0m0.080s
sys 0m0.140s
The command line is more reminiscent of git-log.
For example you can say
git-rewrite-commits --all
to incorporate grafts in all branches, or
git rewrite-commits --author='!Darl McBribe' --all
to remove all commits by Darl McBribe.
Signed-off-by: Sven Verdoolaege <skimo@kotnet.org>
---
.gitignore | 1 +
Documentation/cmd-list.perl | 1 +
Documentation/git-rewrite-commits.txt | 147 +++++++++++
Makefile | 1 +
builtin-rewrite-commits.c | 451 +++++++++++++++++++++++++++++++++
builtin.h | 1 +
git.c | 1 +
t/t7005-rewrite-commits.sh | 74 ++++++
8 files changed, 677 insertions(+), 0 deletions(-)
create mode 100644 Documentation/git-rewrite-commits.txt
create mode 100644 builtin-rewrite-commits.c
create mode 100755 t/t7005-rewrite-commits.sh
diff --git a/.gitignore b/.gitignore
index 20ee642..bcd95a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -109,6 +109,7 @@ git-reset
git-rev-list
git-rev-parse
git-revert
+git-rewrite-commits
git-rm
git-runstatus
git-send-email
diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl
index 2143995..63911b7 100755
--- a/Documentation/cmd-list.perl
+++ b/Documentation/cmd-list.perl
@@ -166,6 +166,7 @@ git-reset mainporcelain
git-revert mainporcelain
git-rev-list plumbinginterrogators
git-rev-parse ancillaryinterrogators
+git-rewrite-commits mainporcelain
git-rm mainporcelain
git-runstatus ancillaryinterrogators
git-send-email foreignscminterface
diff --git a/Documentation/git-rewrite-commits.txt b/Documentation/git-rewrite-commits.txt
new file mode 100644
index 0000000..d82b777
--- /dev/null
+++ b/Documentation/git-rewrite-commits.txt
@@ -0,0 +1,147 @@
+git-rewrite-commits(1)
+======================
+
+NAME
+----
+git-rewrite-commits - Rewrite commits
+
+SYNOPSIS
+--------
+'git-rewrite-commits' [--index-map <command>] [--commit-map <command>]
+ [<rev-list options>...]
+
+DESCRIPTION
+-----------
+Lets you rewrite the commits selected by the gitlink:git-rev-list[1]
+options, optionally applying custom maps on each of them.
+These maps either modify the tree associated to a commit
+(through manipulations of the index) or the (raw) commit itself.
+Any commit within the specified range that is filtered out
+through a pattern is removed from its children (assuming they
+are in range) and replaced by the closest ancestors that
+are either not being rewritten or not filtered out.
+
+Any branch pointing to any of the rewritten commits is replaced
+by a pointer to the new commit. The original pointers are saved
+in the refs/rewritten hierarchy. Any SHA1 in the commit message
+of any of the rewritten commits that points to (another) rewritten
+commit is replaced by the SHA1 of the corresponding new commit.
+
+Besides the actions specified by the maps, the new commits
+will also reflect any grafts that may apply to any of the selected commits.
+
+*WARNING*! The rewritten history will have different object names for all
+the objects and will not converge with the original branch. You will not
+be able to easily push and distribute the rewritten branch on top of the
+original branch. Please do not use this command if you do not know the
+full implications, and avoid using it anyway, if a simple single commit
+would suffice to fix your problem.
+
+Maps
+~~~~
+
+The maps are applied in the order as listed below. The <command>
+argument is run as "sh -c '<command'>".
+
+
+OPTIONS
+-------
+
+--index-map <command>::
+ This map should only modify the index specified by 'GIT_INDEX_FILE'.
+ Prior to running the map, this temporary index is populated
+ with the tree of the commit that is being rewritten.
+ This tree will be replaced according to the new state of this
+ temporary index after the map finishes.
+
+--commit-map <command>::
+ This map receives the (raw) commit on stdin and should produce
+ the (possibly) modified commit on stdout. In the input commit,
+ the tree has already been modified by the index map (if any)
+ and has all references to older commits (including the parents)
+ changed to the possibly rewritten commits.
+
+<rev-list-options>::
+ Selects the commits to be rewritten, defaulting to the history
+ that lead to HEAD. If commits are filtered using a (negative)
+ pattern then all the commits filtered out will be removed
+ from the history of the selected commits.
+
+
+Examples
+--------
+
+Suppose you want to remove a file (containing confidential information
+or copyright violation) from all commits:
+
+--------------------------------------------------------------------
+git rewrite-commits --index-map 'git update-index --remove filename'
+--------------------------------------------------------------------
+
+Now, you will get the rewritten history saved in your current branch
+(the old branch is saved in refs/rewritten).
+
+To set a commit "$graft-id" (which typically is at the tip of another
+history) to be the parent of the current initial commit "$commit-id", in
+order to paste the other history behind the current history:
+
+-----------------------------------------------
+echo "$commit-id $graft-id" >> .git/info/grafts
+git rewrite-commits
+-----------------------------------------------
+
+To remove commits authored by "Darl McBribe" from the history:
+
+--------------------------------------------
+git rewrite-commits --author='!Darl McBribe'
+--------------------------------------------
+
+Note that the changes introduced by the commits, and not reverted by
+subsequent commits, will still be in the rewritten branch. If you want
+to throw out _changes_ together with the commits, you should use the
+interactive mode of gitlink:git-rebase[1].
+
+Consider this history:
+
+------------------
+ D--E--F--G--H
+ / /
+A--B-----C
+------------------
+
+To rewrite only commits D,E,F,G,H, but leave A, B and C alone, use:
+
+--------------------------------
+git rewrite-commits ... C..H
+--------------------------------
+
+To rewrite commits E,F,G,H, use one of these:
+
+----------------------------------------
+git rewrite-commits ... C..H --not D
+git rewrite-commits ... D..H --not C
+----------------------------------------
+
+To move the whole tree into a subdirectory, or remove it from there:
+
+---------------------------------------------------------------
+git rewrite-commits --index-map \
+ 'git ls-files -s | sed "s-\t-&newsubdir/-" |
+ GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
+ git update-index --index-info &&
+ mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE'
+---------------------------------------------------------------
+
+
+Author
+------
+Written by Sven Verdoolaege.
+Inspired by cg-admin-rewritehist by Petr "Pasky" Baudis <pasky@suse.cz>.
+
+Documentation
+--------------
+Documentation by Petr Baudis, Sven Verdoolaege and the git list.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Makefile b/Makefile
index 5b30e5c..f84bcd7 100644
--- a/Makefile
+++ b/Makefile
@@ -370,6 +370,7 @@ BUILTIN_OBJS = \
builtin-rev-list.o \
builtin-rev-parse.o \
builtin-revert.o \
+ builtin-rewrite-commits.o \
builtin-rm.o \
builtin-runstatus.o \
builtin-shortlog.o \
diff --git a/builtin-rewrite-commits.c b/builtin-rewrite-commits.c
new file mode 100644
index 0000000..eebf816
--- /dev/null
+++ b/builtin-rewrite-commits.c
@@ -0,0 +1,451 @@
+#include "cache.h"
+#include "refs.h"
+#include "builtin.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "list-objects.h"
+#include "run-command.h"
+#include "cache-tree.h"
+#include "grep.h"
+
+struct decoration rewrite_decoration = { "rewritten as" };
+static const char *rewritten_prefix = "rewritten";
+static const char *commit_map;
+static const char *index_map;
+
+struct rewrite_decoration {
+ struct rewrite_decoration *next;
+ unsigned char sha1[20];
+};
+
+static void add_rewrite_decoration(struct object *obj, unsigned char *sha1)
+{
+ struct rewrite_decoration *deco = xmalloc(sizeof(struct rewrite_decoration));
+ hashcpy(deco->sha1, sha1);
+ deco->next = add_decoration(&rewrite_decoration, obj, deco);
+}
+
+static int get_rewritten_sha1(unsigned char *sha1)
+{
+ struct rewrite_decoration *deco;
+ struct object *obj = lookup_object(sha1);
+
+ if (!obj)
+ return 1;
+
+ deco = lookup_decoration(&rewrite_decoration, obj);
+ if (!deco)
+ return 1;
+
+ hashcpy(sha1, deco->sha1);
+ return 0;
+}
+
+static char *add_parents(char *dest, struct commit_list *parents)
+{
+ unsigned char sha1[20];
+ struct commit_list *list;
+
+ for (list = parents; list; list = list->next) {
+ hashcpy(sha1, list->item->object.sha1);
+ get_rewritten_sha1(sha1);
+ memcpy(dest, "parent ", 7);
+ dest += 7;
+ memcpy(dest, sha1_to_hex(sha1), 40);
+ dest += 40;
+ *dest++ = '\n';
+ }
+ return dest;
+}
+
+static int get_one_line(char *buf, unsigned long len)
+{
+ char *end = memchr(buf, '\n', len);
+ if (end)
+ return end - buf + 1;
+ return len;
+}
+
+static char *map_index(char *orig_hex)
+{
+ int argc;
+ const char *argv[10];
+ static char index_env[16+PATH_MAX];
+ char *tmp_index = git_path("rewrite_index");
+ const char *env[] = { index_env, NULL };
+ char *hex;
+
+ memcpy(index_env, "GIT_INDEX_FILE=", 15);
+ strcpy(index_env+15, tmp_index);
+ tmp_index = index_env+15;
+
+ /* First write out tree to temporary index */
+ argc = 0;
+ argv[argc++] = "read-tree";
+ argv[argc++] = orig_hex;
+ argv[argc] = NULL;
+ if (run_command_v_opt_cd_env(argv, RUN_GIT_CMD, NULL, env))
+ die("Cannot write index '%s' for map", tmp_index);
+
+ /* Then map the index */
+ argc = 0;
+ argv[argc++] = "sh";
+ argv[argc++] = "-c";
+ argv[argc++] = index_map;
+ argv[argc] = NULL;
+ if (run_command_v_opt_cd_env(argv, 0, NULL, env))
+ die("Index map '%s' failed", index_map);
+
+ /* Finally read it back in */
+ if (read_cache_from(tmp_index) < 0)
+ die("Error reading index '%s'", tmp_index);
+ active_cache_tree = cache_tree();
+ if (cache_tree_update(active_cache_tree, active_cache, active_nr,
+ 0, 0) < 0)
+ die("Error building trees");
+ hex = sha1_to_hex(active_cache_tree->sha1);
+ discard_cache();
+
+ unlink(tmp_index);
+
+ return hex;
+}
+
+static char *rewrite_header(char *dest, unsigned long *len_p, char **buf_p,
+ struct commit_list *parents)
+{
+ int linelen;
+ do {
+ char *line = *buf_p;
+ linelen = get_one_line(*buf_p, *len_p);
+
+ if (!linelen)
+ return dest;
+ *buf_p += linelen;
+ *len_p -= linelen;
+
+ if (index_map && !memcmp(line, "tree ", 5)) {
+ if (linelen != 46)
+ die("bad tree line in commit");
+ memcpy(dest, "tree ", 5);
+ line[45] = '\0';
+ memcpy(dest+5, map_index(line+5), 40);
+ line[45] = '\n';
+ dest[45] = '\n';
+ dest += 46;
+ continue;
+ }
+
+ /* drop old parents */
+ if (!memcmp(line, "parent ", 7)) {
+ if (linelen != 48)
+ die("bad parent line in commit");
+ continue;
+ }
+
+ /* insert new parents before author */
+ if (!memcmp(line, "author ", 7))
+ dest = add_parents(dest, parents);
+
+ memcpy(dest, line, linelen);
+ dest += linelen;
+ } while (linelen > 1);
+ return dest;
+}
+
+static size_t memspn(const char *s, const char *accept, size_t n)
+{
+ size_t offset;
+
+ for (offset = 0; offset < n; ++offset)
+ if (!strchr(accept, s[offset]))
+ break;
+ return offset;
+}
+
+static size_t memcspn(const char *s, const char *accept, size_t n)
+{
+ size_t offset;
+
+ for (offset = 0; offset < n; ++offset)
+ if (strchr(accept, s[offset]))
+ break;
+ return offset;
+}
+
+/* Replace any (short) sha1 of a rewritten commit by the new (short) sha1 */
+static char *rewrite_body(char *dest, unsigned long len, char *buf)
+{
+ static const char hex[] = "0123456789abcdef";
+ unsigned char sha1[20];
+
+ while (len) {
+ size_t ll = memcspn(buf, hex, len);
+ memcpy(dest, buf, ll);
+ dest += ll;
+ buf += ll;
+ len -= ll;
+
+ ll = memspn(buf, hex, len);
+ if (ll >= 8 && ll <= 40 &&
+ !get_short_sha1(buf, ll, sha1, 1) &&
+ !get_rewritten_sha1(sha1))
+ memcpy(dest, sha1_to_hex(sha1), ll);
+ else
+ memcpy(dest, buf, ll);
+ dest += ll;
+ buf += ll;
+ len -= ll;
+ }
+ return dest;
+}
+
+static void write_ref_sha1_or_die(const char *ref, const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *logmsg, int flags)
+{
+ struct ref_lock *lock;
+
+ lock = lock_any_ref_for_update(ref, old_sha1, flags);
+ if (!lock)
+ die("%s: cannot lock the ref", ref);
+ if (write_ref_sha1(lock, new_sha1, logmsg) < 0)
+ die("%s: cannot update the ref", ref);
+}
+
+static int is_ref_to_be_rewritten(const char *ref)
+{
+ unsigned char sha1[20];
+ int flag;
+
+ if (prefixcmp(ref, "refs/"))
+ return 0;
+ if (!prefixcmp(ref, "refs/remotes/"))
+ return 0;
+
+ resolve_ref(ref, sha1, 0, &flag);
+ if (flag & REF_ISSYMREF)
+ return 0;
+
+ return 1;
+}
+
+static void rewrite_sha1(struct object *obj, unsigned char *new_sha1)
+{
+ struct name_decoration *deco;
+ int prefix_len;
+
+ if (!hashcmp(obj->sha1, new_sha1))
+ return;
+
+ add_rewrite_decoration(obj, new_sha1);
+
+ prefix_len = strlen(rewritten_prefix);
+ deco = lookup_decoration(&name_decoration, obj);
+ for (; deco; deco = deco->next) {
+ char buffer[256];
+ char *p;
+ int len = strlen(deco->name);
+
+ if (!is_ref_to_be_rewritten(deco->name))
+ continue;
+ if (len + prefix_len + 1 + 1 > sizeof(buffer))
+ die("rewrite ref of '%s' too long", deco->name);
+ p = buffer;
+ memcpy(p, "refs/", 5);
+ p += 5;
+ memcpy(p, rewritten_prefix, prefix_len);
+ p += prefix_len;
+ memcpy(p, deco->name+4, len-4 + 1);
+
+ write_ref_sha1_or_die(buffer, NULL, obj->sha1,
+ "copied during rewrite", 0);
+
+ if (is_null_sha1(new_sha1))
+ delete_ref(deco->name, obj->sha1);
+ else
+ write_ref_sha1_or_die(deco->name, obj->sha1, new_sha1,
+ "rewritten", 0);
+ }
+}
+
+static void map_and_write_commit(char *commit, size_t len, unsigned char *sha1)
+{
+ char commit_path[PATH_MAX];
+ struct child_process cmd;
+ int argc;
+ const char *argv[10];
+ int fd;
+
+ fd = git_mkstemp(commit_path, sizeof(commit_path), ".commit_XXXXXX");;
+ write_or_die(fd, commit, len);
+
+ argc = 0;
+ argv[argc++] = "sh";
+ argv[argc++] = "-c";
+ argv[argc++] = commit_map;
+ argv[argc] = NULL;
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.in = open(commit_path, O_RDONLY);
+ if (cmd.in < 0)
+ die("Unable to read commit from file '%s'", commit_path);
+ unlink(commit_path);
+ cmd.out = -1;
+ cmd.argv = argv;
+ if (start_command(&cmd))
+ die("Commit map '%s' failed to start", commit_map);
+ if (index_pipe(sha1, cmd.out, commit_type, 1))
+ die("Unable to write new commit");
+ if (finish_command(&cmd))
+ die("Commit map '%s' failed", commit_map);
+ close(cmd.in);
+}
+
+/*
+ * Replace any parent that has been removed by its parents
+ * and return the number of new parents.
+ * We directly modify the parent list, so any libification
+ * should probably adapt this function.
+ */
+static int rewrite_parents(struct commit *commit)
+{
+ int n;
+ struct commit_list *list, *parents, **prev;
+ unsigned char sha1[20];
+
+ for (n = 0, prev = &commit->parents; *prev; ++n) {
+ list = *prev;
+
+ if (!(list->item->object.flags & PRUNED)) {
+ prev = &list->next;
+ continue;
+ }
+
+ hashcpy(sha1, list->item->object.sha1);
+ get_rewritten_sha1(sha1);
+ if (!is_null_sha1(sha1)) {
+ hashclr(sha1);
+ rewrite_sha1(&list->item->object, sha1);
+ }
+
+ parents = list->item->parents;
+ if (!parents) {
+ *prev = list->next;
+ free(list);
+ --n;
+ continue;
+ }
+ list->item = parents->item;
+ prev = &list->next;
+ list = list->next;
+ while ((parents = parents->next)) {
+ commit_list_insert(parents->item, prev);
+ prev = &(*prev)->next;
+ ++n;
+ }
+ *prev = list;
+ }
+
+ return n;
+}
+
+static void rewrite_commit(struct commit *commit)
+{
+ char *buf, *p;
+ int n;
+ char *orig_buf = commit->buffer;
+ unsigned long orig_len = strlen(orig_buf);
+ unsigned char sha1[20];
+
+ n = rewrite_parents(commit);
+
+ /* Make enough remove for n (possibly extra) parents */
+ p = buf = xmalloc(orig_len + n*48);
+ p = rewrite_header(p, &orig_len, &orig_buf, commit->parents);
+ p = rewrite_body(p, orig_len, orig_buf);
+ if (!commit_map) {
+ if (write_sha1_file(buf, p-buf, commit_type, sha1))
+ die("Unable to write new commit");
+ } else
+ map_and_write_commit(buf, p-buf, sha1);
+ free(buf);
+
+ rewrite_sha1(&commit->object, sha1);
+}
+
+static void move_head_forward(char *ref, unsigned char *old_sha1, int detached)
+{
+ unsigned char new_sha1[20];
+ int rewritten;
+
+ hashcpy(new_sha1, old_sha1);
+ rewritten = !get_rewritten_sha1(new_sha1);
+ if (rewritten && !is_bare_repository()) {
+ int argc;
+ const char *argv[10];
+
+ argc = 0;
+ argv[argc++] = "read-tree";
+ argv[argc++] = "-m";
+ argv[argc++] = "-u";
+ argv[argc++] = sha1_to_hex(old_sha1);
+ argv[argc++] = sha1_to_hex(new_sha1);
+ argv[argc] = NULL;
+ if (run_command_v_opt(argv, RUN_GIT_CMD))
+ die("Cannot move HEAD forward");
+ }
+ if (!detached)
+ create_symref("HEAD", ref, "reattached after rewrite");
+ else if (rewritten)
+ write_ref_sha1_or_die("HEAD", NULL, new_sha1,
+ "rewritten", REF_NODEREF);
+}
+
+int cmd_rewrite_commits(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ unsigned char HEAD_sha1[20];
+ char *HEAD_ref;
+ int flag;
+ int i, j;
+
+ init_revisions(&rev, prefix);
+
+ for (i = 1, j = 1; i < argc; ++i) {
+ if (!strcmp(argv[i], "--index-map")) {
+ if (++i == argc)
+ die("Argument required for --index-map");
+ index_map = argv[i];
+ } else if (!strcmp(argv[i], "--commit-map")) {
+ if (++i == argc)
+ die("Argument required for --commit-map");
+ commit_map = argv[i];
+ } else
+ argv[j++] = argv[i];
+ }
+ argc = j;
+ argc = setup_revisions(argc, argv, &rev, "HEAD");
+ rev.ignore_merges = 0;
+ rev.topo_order = 1;
+ rev.reverse = 1;
+
+ for_each_ref(add_ref_decoration, NULL);
+
+ HEAD_ref = xstrdup(resolve_ref("HEAD", HEAD_sha1, 1, &flag));
+ if (flag & REF_ISSYMREF) {
+ /* Detach HEAD at its current position */
+ write_ref_sha1_or_die("HEAD", NULL, HEAD_sha1,
+ "detached for rewrite", REF_NODEREF);
+ }
+
+ prepare_revision_walk(&rev);
+ while ((commit = get_revision(&rev)) != NULL) {
+ rewrite_commit(commit);
+ }
+
+ move_head_forward(HEAD_ref, HEAD_sha1, !(flag & REF_ISSYMREF));
+
+ return 0;
+}
diff --git a/builtin.h b/builtin.h
index 661a92f..e01d337 100644
--- a/builtin.h
+++ b/builtin.h
@@ -63,6 +63,7 @@ extern int cmd_rerere(int argc, const char **argv, const char *prefix);
extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
extern int cmd_revert(int argc, const char **argv, const char *prefix);
+extern int cmd_rewrite_commits(int argc, const char **argv, const char *prefix);
extern int cmd_rm(int argc, const char **argv, const char *prefix);
extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
diff --git a/git.c b/git.c
index a647f9c..ce02b0b 100644
--- a/git.c
+++ b/git.c
@@ -356,6 +356,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "rev-list", cmd_rev_list, RUN_SETUP },
{ "rev-parse", cmd_rev_parse, RUN_SETUP },
{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
+ { "rewrite-commits", cmd_rewrite_commits, RUN_SETUP },
{ "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
{ "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
diff --git a/t/t7005-rewrite-commits.sh b/t/t7005-rewrite-commits.sh
new file mode 100755
index 0000000..93f438f
--- /dev/null
+++ b/t/t7005-rewrite-commits.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+test_description='git-rewrite-commits'
+. ./test-lib.sh
+
+make_commit () {
+ lower=$(echo $1 | tr A-Z a-z)
+ echo $lower > $lower
+ git add $lower
+ test_tick
+ git commit -m $1
+ git tag $1
+}
+
+test_expect_success 'setup' '
+ make_commit A
+ make_commit B
+ git checkout -b branch B
+ make_commit D
+ make_commit E
+ git checkout master
+ make_commit C
+ git checkout branch
+ git merge C
+ git tag F
+ make_commit G
+ make_commit H
+'
+
+orig_H=$(git rev-parse H)
+test_expect_success 'rewrite identically' '
+ git-rewrite-commits
+'
+
+test_expect_success 'result is really identical' '
+ test $orig_H = $(git rev-parse H)
+'
+
+# for lack of 'git-mv --cached d doh'
+test_expect_success 'rewrite, renaming a specific file' '
+ git-rewrite-commits --index-map \
+ "git-ls-files -s | sed \"s-\\td\\\$-\\tdoh-\" |
+ GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \
+ git-update-index --index-info &&
+ mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE"
+'
+
+test_expect_success 'test that the file was renamed' '
+ test d = $(git-show H:doh) &&
+ ! git-show H:d --
+'
+
+orig_H=$(git rev-parse H)
+test_expect_success 'use index-map to move into a subdirectory' '
+ git-rewrite-commits --index-map \
+ "git ls-files -s | sed \"s-\\t-&newsubdir/-\" |
+ GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \
+ git update-index --index-info &&
+ mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" &&
+ test -z "$(git diff $orig_H H:newsubdir)"'
+
+test_expect_success 'remove merge commit' '
+ git-rewrite-commits --grep="!Merge" &&
+ test 2 = `git-log ^G^@ G --pretty=format:%P | wc -w`'
+
+test_expect_success 'remove first commit' '
+ git-rewrite-commits --grep="!A"'
+
+test_expect_success 'stops when index map fails' '
+ ! git-rewrite-commits --index-map false &&
+ git-checkout branch
+'
+
+test_done
--
1.5.3.rc0.68.geec71-dirty
^ permalink raw reply related
* [PATCH 3/4] revision: mark commits that didn't match a pattern for later use
From: skimo @ 2007-07-08 16:23 UTC (permalink / raw)
To: git, Junio C Hamano
In-Reply-To: <11839118073186-git-send-email-skimo@liacs.nl>
From: Sven Verdoolaege <skimo@kotnet.org>
Signed-off-by: Sven Verdoolaege <skimo@kotnet.org>
---
revision.c | 4 +++-
revision.h | 1 +
2 files changed, 4 insertions(+), 1 deletions(-)
diff --git a/revision.c b/revision.c
index 0035d40..8d3bed2 100644
--- a/revision.c
+++ b/revision.c
@@ -1421,8 +1421,10 @@ static struct commit *get_revision_1(struct rev_info *revs)
if (revs->no_merges &&
commit->parents && commit->parents->next)
continue;
- if (!commit_match(commit, revs))
+ if (!commit_match(commit, revs)) {
+ commit->object.flags |= PRUNED;
continue;
+ }
if (revs->prune_fn && revs->dense) {
/* Commit without changes? */
if (!(commit->object.flags & TREECHANGE)) {
diff --git a/revision.h b/revision.h
index 9728d4c..d3609ab 100644
--- a/revision.h
+++ b/revision.h
@@ -10,6 +10,7 @@
#define CHILD_SHOWN (1u<<6)
#define ADDED (1u<<7) /* Parents already parsed and added? */
#define SYMMETRIC_LEFT (1u<<8)
+#define PRUNED (1u<<9)
struct rev_info;
struct log_info;
--
1.5.3.rc0.68.geec71-dirty
^ permalink raw reply related
* [PATCH 1/4] export get_short_sha1
From: skimo @ 2007-07-08 16:23 UTC (permalink / raw)
To: git, Junio C Hamano
In-Reply-To: <11839118073186-git-send-email-skimo@liacs.nl>
From: Sven Verdoolaege <skimo@kotnet.org>
Sometimes it is useful to check whether a given string
is exactly a short SHA1.
Signed-off-by: Sven Verdoolaege <skimo@kotnet.org>
---
cache.h | 1 +
sha1_name.c | 3 +--
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/cache.h b/cache.h
index e64071e..8fda8ee 100644
--- a/cache.h
+++ b/cache.h
@@ -391,6 +391,7 @@ static inline unsigned int hexval(unsigned char c)
extern int get_sha1(const char *str, unsigned char *sha1);
extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode);
+extern int get_short_sha1(const char *name, int len, unsigned char *sha1, int quietly);
extern int get_sha1_hex(const char *hex, unsigned char *sha1);
extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
extern int read_ref(const char *filename, unsigned char *sha1);
diff --git a/sha1_name.c b/sha1_name.c
index 858f08c..0bed79d 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -154,8 +154,7 @@ static int find_unique_short_object(int len, char *canonical,
return 0;
}
-static int get_short_sha1(const char *name, int len, unsigned char *sha1,
- int quietly)
+int get_short_sha1(const char *name, int len, unsigned char *sha1, int quietly)
{
int i, status;
char canonical[40];
--
1.5.3.rc0.68.geec71-dirty
^ permalink raw reply related
* Re: [PATCH 4/4] Add git-rewrite-commits
From: Johannes Schindelin @ 2007-07-08 16:37 UTC (permalink / raw)
To: skimo; +Cc: git, Junio C Hamano
In-Reply-To: <1183911808787-git-send-email-skimo@liacs.nl>
Hi,
On Sun, 8 Jul 2007, skimo@liacs.nl wrote:
> bash-3.00$ time git-filter-branch test
> Rewrite 274fe3dfb8e8c7d0a6ce05138bdb650de7b459ea (425/425)
> Rewritten history saved to the test branch
>
> real 0m30.845s
> user 0m13.400s
> sys 0m19.640s
>
> bash-3.00$ time git-rewrite-commits
>
> real 0m0.223s
> user 0m0.080s
> sys 0m0.140s
That is to be expected. After all, the first is a script. However, I
really have ask: how often per hour do you want to run that program?
> The command line is more reminiscent of git-log.
> For example you can say
>
> git-rewrite-commits --all
>
> to incorporate grafts in all branches, or
>
> git rewrite-commits --author='!Darl McBribe' --all
>
> to remove all commits by Darl McBribe.
I am really unhappy that so much is talked about filtering out commits.
That is amost certainly not what you want in most cases. In particular, I
suspect that most users would expect the _changes_ filtered out by such a
command, which is just not true.
Further, I do not see much value in making this operation faster. It is
meant to be a one-time operation, for example when you open-source a
product and have to cull a lot of history that you must not show for legal
reasons. It is a one-shot operation. And having a script which works
reliably is more valuable than having two programs, each with its own set
of bugs.
So there are two things I see here that filter-branch cannot do yet. The
first is to rewrite _all_ branches, which should be easy to do, it only
has to be done.
The second is to rewrite the commit messages so that the hashes are
mapped, too. But that should be relatively easy, too: you can provide a
message filter, and you can use the provided "map" function. If this
seems to be what many people need, you can write a simple function and put
it into filter-branch for common use.
BTW I am not at all opposed to changing the name from filter-branch to
something that fits better.
Ciao,
Dscho
^ permalink raw reply
* Re: [PATCH 4/4] Add git-rewrite-commits
From: Sven Verdoolaege @ 2007-07-08 17:30 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: git, Junio C Hamano
In-Reply-To: <Pine.LNX.4.64.0707081729040.4248@racer.site>
On Sun, Jul 08, 2007 at 05:37:22PM +0100, Johannes Schindelin wrote:
> That is to be expected. After all, the first is a script. However, I
> really have ask: how often per hour do you want to run that program?
I have a project that needs some cleaning-up and I'd like to do
it incrementally. I think I'll have to run it about a dozen times.
> I am really unhappy that so much is talked about filtering out commits.
> That is amost certainly not what you want in most cases. In particular, I
> suspect that most users would expect the _changes_ filtered out by such a
> command, which is just not true.
I don't care about that either. I'm just mentioning it because
it's mentioned in the git-filter-branch documentation (which you
added).
> The second is to rewrite the commit messages so that the hashes are
> mapped, too. But that should be relatively easy, too: you can provide a
> message filter, and you can use the provided "map" function. If this
> seems to be what many people need, you can write a simple function and put
> it into filter-branch for common use.
It's not going to be me (as I sais, I don't like shell programming).
skimo
^ permalink raw reply
* [RFC][PATCH] Re: git-rm isn't the inverse action of git-add
From: Matthieu Moy @ 2007-07-08 17:36 UTC (permalink / raw)
To: git; +Cc: Jan Hudec, Johannes Schindelin, Yann Dirson, Christian Jaeger
In-Reply-To: <vpqd4z7q820.fsf@bauges.imag.fr>
Matthieu Moy <Matthieu.Moy@imag.fr> writes:
>> - if (HEAD == index && index == version) unversion and unlink
>
> Just to be more precise:
>
> - if (HEAD == index && index == version) unversion and
> * if (--cached is not given) unlink
> * else do nothing
>
>> - else if (HEAD == index || index == version) unversion
>> - else print message and do nothing
>>
>> Would you consider that a sane behaviour?
[...]
> I'll try writting patch for that if people agree that this is saner
> that the current behavior.
Here's a first attempt (I'm still not familiar with the git codebase,
so the patch is probably not so good).
Note: currently, git-rm still shows those "rm '...'" messages on
stdout. AAUI, they were actually useful at a time when git-rm didn't
actually remove the files, and people actually ran the "rm" commands
after. They can probably be removed now, but that's another topic.
>From f4f4aa047b2b9050d968704d1f2db07b2a1a79cc Mon Sep 17 00:00:00 2001
From: Matthieu Moy <Matthieu.Moy@imag.fr>
Date: Sun, 8 Jul 2007 19:27:44 +0200
Subject: [PATCH] Make git-rm obey in more circumstances.
In the previous behavior of git-rm, git refused to do anything in case of
a difference between the file on disk, the index, and the HEAD. As a
result, the -f flag is forced even for simple senarios like:
$ git add foo
# oops, I didn't want to version it
$ git rm -f [--cached] foo
# foo is deleted on disk if --cached isn't provided.
This patch proposes a saner behavior. When there are no difference at all
between file, index and HEAD, the file is removed both from the index and
the tree, as before.
Otherwise, if the index matches either the file on disk or the HEAD, the
file is removed from the index, but the file is kept on disk, it may
contain important data.
Otherwise, that's an error, and git-rm aborts.
The above senario becomes
$ git add foo
$ git rm foo
# back to the initial state.
Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
Documentation/git-rm.txt | 9 ++++---
builtin-rm.c | 55 ++++++++++++++++++++++++++++++++++++---------
2 files changed, 49 insertions(+), 15 deletions(-)
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
index 78f45dc..180671c 100644
--- a/Documentation/git-rm.txt
+++ b/Documentation/git-rm.txt
@@ -11,10 +11,11 @@ SYNOPSIS
DESCRIPTION
-----------
-Remove files from the working tree and from the index. The
-files have to be identical to the tip of the branch, and no
-updates to its contents must have been placed in the staging
-area (aka index).
+Remove files from the working tree and from the index. The content
+placed in the staging area (aka index) must match either the content
+of the file on disk, or the tip of the branch. If it matches only one
+of them, the file is kept on disk for safety, but is still removed
+from the index.
OPTIONS
diff --git a/builtin-rm.c b/builtin-rm.c
index 4a0bd93..d2a8998 100644
--- a/builtin-rm.c
+++ b/builtin-rm.c
@@ -12,18 +12,27 @@
static const char builtin_rm_usage[] =
"git-rm [-f] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>...";
+struct file_info {
+ const char *name;
+ int local_changes;
+ int staged_changes;
+};
+
static struct {
int nr, alloc;
- const char **name;
+ struct file_info * files;
} list;
static void add_list(const char *name)
{
if (list.nr >= list.alloc) {
list.alloc = alloc_nr(list.alloc);
- list.name = xrealloc(list.name, list.alloc * sizeof(const char *));
+ list.files = xrealloc(list.files, list.alloc * sizeof(const char *));
}
- list.name[list.nr++] = name;
+ list.files[list.nr].name = name;
+ list.files[list.nr].local_changes = 0;
+ list.files[list.nr].staged_changes = 0;
+ list.nr++;
}
static int remove_file(const char *name)
@@ -46,6 +55,26 @@ static int remove_file(const char *name)
return ret;
}
+static int remove_file_maybe(const struct file_info fi, int quiet)
+{
+ const char *path = fi.name;
+ if (!fi.local_changes && !fi.staged_changes) {
+ /* The file matches either the index or the HEAD.
+ * It's content exists somewhere else, it's safe to
+ * delete it.
+ */
+ return remove_file(path);
+ } else {
+ if (!quiet)
+ fprintf(stderr,
+ "note: file '%s' not removed "
+ "(doesn't match %s).\n",
+ path,
+ fi.local_changes?"the index":"HEAD");
+ return 0;
+ }
+}
+
static int check_local_mod(unsigned char *head)
{
/* items in list are already sorted in the cache order,
@@ -62,7 +91,7 @@ static int check_local_mod(unsigned char *head)
struct stat st;
int pos;
struct cache_entry *ce;
- const char *name = list.name[i];
+ const char *name = list.files[i].name;
unsigned char sha1[20];
unsigned mode;
@@ -87,13 +116,17 @@ static int check_local_mod(unsigned char *head)
continue;
}
if (ce_match_stat(ce, &st, 0))
- errs = error("'%s' has local modifications "
- "(hint: try -f)", ce->name);
+ list.files[i].local_changes = 1;
+
if (no_head
|| get_tree_entry(head, name, sha1, &mode)
|| ce->ce_mode != create_ce_mode(mode)
|| hashcmp(ce->sha1, sha1))
- errs = error("'%s' has changes staged in the index "
+ list.files[i].staged_changes = 1;
+
+ if (list.files[i].local_changes &&
+ list.files[i].staged_changes)
+ errs = error("'%s' doesn't match neither HEAD nor the index "
"(hint: try -f)", name);
}
return errs;
@@ -201,7 +234,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
* the index unless all of them succeed.
*/
for (i = 0; i < list.nr; i++) {
- const char *path = list.name[i];
+ const char *path = list.files[i].name;
if (!quiet)
printf("rm '%s'\n", path);
@@ -224,13 +257,13 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (!index_only) {
int removed = 0;
for (i = 0; i < list.nr; i++) {
- const char *path = list.name[i];
- if (!remove_file(path)) {
+ if (!remove_file_maybe(list.files[i], quiet)) {
removed = 1;
continue;
}
if (!removed)
- die("git-rm: %s: %s", path, strerror(errno));
+ die("git-rm: %s: %s",
+ list.files[i].name, strerror(errno));
}
}
--
1.5.3.rc0.63.gc956-dirty
--
Matthieu
^ permalink raw reply related
* Re: [PATCH 4/4] Add git-rewrite-commits
From: Steven Grimm @ 2007-07-08 18:04 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: skimo, git, Junio C Hamano
In-Reply-To: <Pine.LNX.4.64.0707081729040.4248@racer.site>
Johannes Schindelin wrote:
> I am really unhappy that so much is talked about filtering out commits.
> That is amost certainly not what you want in most cases. In particular, I
> suspect that most users would expect the _changes_ filtered out by such a
> command, which is just not true.
>
I agree that unless it's named and documented very carefully, users
might expect this to tweak history such that the commits in question
never happened (unlike revert, which of course adds a new commit and
leaves the old ones alone.) The documentation for this command could
stand to be more explicit about that.
> Further, I do not see much value in making this operation faster. It is
> meant to be a one-time operation, for example when you open-source a
> product and have to cull a lot of history that you must not show for legal
> reasons. It is a one-shot operation.
>
Your recent changes to git-rebase (which, BTW, are great) include a
feature that's very similar to this: the "squash these commits together
in my history" feature. That'd be my use case for this, when I want to
publish my changes to other developers who don't care about all my
intermediate checkpoints of work in progress, and when the commits I'm
removing haven't been published anywhere else yet.
With this command, I could do something like:
git rewrite-commits --grep="!@@@checkpoint"
git push
and it would strip out all my intermediate checkpoint commits (assuming
I've marked them as such in my commit comments, which I always do)
before pushing to my project's shared repo. Right now that's a much more
cumbersome, and very manual, operation. Even with the new git-rebase
changes, I still have to pick out those commits by hand, and it assumes
that I otherwise want to do a rebase in the first place.
> So there are two things I see here that filter-branch cannot do yet. The
> first is to rewrite _all_ branches, which should be easy to do, it only
> has to be done.
>
I wonder if it makes sense to go that direction, though, or to make this
command do the things that filter-branch can do, for the simple reason
that filter-branch is a shell script and this is already a nice
non-shell-dependent C program. Obviously you end up in the same place
either way eventually once filter-branch percolates to the top of the
"port these scripts to C" list, but it seems odd to me to port features
from a C program back to a shell script only to have to convert the
shell script to C later on.
Ironically, this app doesn't really speed up the one thing I find too
slow in filter-branch: the "remove a file from the tree in all
revisions" case. To do that you still have to launch a filter app for
every commit, which is especially bad when the file in question only
appears in a few revisions deep in the history of a repo.
This command points us in the direction of a "remove/rename this file in
history" feature that doesn't require forking tens of thousands of child
processes on a repo with lots of history. For that alone I think it's
worthwhile, even though it's not there yet; that will never happen with
a shell script. And yeah, that's not a frequent operation, but it's sure
nice when even the infrequent operations are lightning fast.
-Steve
^ permalink raw reply
* Re: [PATCH 4/4] Add git-rewrite-commits
From: Sven Verdoolaege @ 2007-07-08 18:15 UTC (permalink / raw)
To: Steven Grimm; +Cc: Johannes Schindelin, git, Junio C Hamano
In-Reply-To: <46912726.5080807@midwinter.com>
On Sun, Jul 08, 2007 at 11:04:22AM -0700, Steven Grimm wrote:
> This command points us in the direction of a "remove/rename this file in
> history" feature that doesn't require forking tens of thousands of child
> processes on a repo with lots of history. For that alone I think it's
> worthwhile, even though it's not there yet; that will never happen with
> a shell script. And yeah, that's not a frequent operation, but it's sure
> nice when even the infrequent operations are lightning fast.
I couldn't come up with a clean interface to specify removes/renames
on the command line (other than copying the external command idea from
cg-admin-rewritehist), but this is one of the operations I have to
perform on the project I'm cleaning up, so if you have any suggestions,
I'd be interested to hear them.
skimo
^ permalink raw reply
* Re: [RFC][PATCH] Re: git-rm isn't the inverse action of git-add
From: Johannes Schindelin @ 2007-07-08 18:10 UTC (permalink / raw)
To: Matthieu Moy; +Cc: git, Jan Hudec, Yann Dirson, Christian Jaeger
In-Reply-To: <vpqfy3yajbj.fsf_-_@bauges.imag.fr>
Hi,
On Sun, 8 Jul 2007, Matthieu Moy wrote:
> Subject: [PATCH] Make git-rm obey in more circumstances.
This is not really a good patch title. Since it only obeys your
particular understanding of what it should do. You are changing
semantics, and you should say so.
> In the previous behavior of git-rm, git refused to do anything in case
> of a difference between the file on disk, the index, and the HEAD. As a
> result, the -f flag is forced even for simple senarios like:
>
> $ git add foo
> # oops, I didn't want to version it
> $ git rm -f [--cached] foo
> # foo is deleted on disk if --cached isn't provided.
>
> This patch proposes a saner behavior. When there are no difference at
> all between file, index and HEAD, the file is removed both from the
> index and the tree, as before.
>
> Otherwise, if the index matches either the file on disk or the HEAD, the
> file is removed from the index, but the file is kept on disk, it may
> contain important data.
However, if some of the files are of the first kind, and some are of the
second kind, you happily apply with mixed strategies. IMO that is wrong.
> static struct {
> int nr, alloc;
> - const char **name;
> + struct file_info * files;
> } list;
>
> static void add_list(const char *name)
> {
> if (list.nr >= list.alloc) {
> list.alloc = alloc_nr(list.alloc);
> - list.name = xrealloc(list.name, list.alloc * sizeof(const char *));
> + list.files = xrealloc(list.files, list.alloc * sizeof(const char *));
This is wrong, too. Yes, it works. But it really should be
"sizeof(struct file_info *)". Remember, code is also documentation.
> +static int remove_file_maybe(const struct file_info fi, int quiet)
> +{
> + const char *path = fi.name;
> + if (!fi.local_changes && !fi.staged_changes) {
> + /* The file matches either the index or the HEAD.
> + * It's content exists somewhere else, it's safe to
> + * delete it.
> + */
> + return remove_file(path);
> + } else {
Superfluous "{ .. }".
> + if (!quiet)
> + fprintf(stderr,
> + "note: file '%s' not removed "
> + "(doesn't match %s).\n",
> + path,
> + fi.local_changes?"the index":"HEAD");
> + return 0;
> + }
> +}
I suspect that this case does never fail. 0 means success for
remove_file(). Not good. You should at least have a way to ensure that
it removed the files from the working tree from a script. Otherwise there
is not much point in returning a value to begin with.
> @@ -224,13 +257,13 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
> if (!index_only) {
> int removed = 0;
> for (i = 0; i < list.nr; i++) {
> - const char *path = list.name[i];
> - if (!remove_file(path)) {
> + if (!remove_file_maybe(list.files[i], quiet)) {
> removed = 1;
> continue;
> }
> if (!removed)
> - die("git-rm: %s: %s", path, strerror(errno));
> + die("git-rm: %s: %s",
> + list.files[i].name, strerror(errno));
> }
> }
Style: the old code set and used "path" for readability. You should do
the same (with "file", probably).
Additionally, since this changes semantics, you better provide test cases
to show what is expected to work, and _ensure_ that it actually works.
Ciao,
Dscho
^ permalink raw reply
* Re: [PATCH 4/4] Add git-rewrite-commits
From: Johannes Schindelin @ 2007-07-08 18:17 UTC (permalink / raw)
To: skimo; +Cc: git, Junio C Hamano
In-Reply-To: <20070708173027.GK1528MdfPADPa@greensroom.kotnet.org>
Hi,
On Sun, 8 Jul 2007, Sven Verdoolaege wrote:
> On Sun, Jul 08, 2007 at 05:37:22PM +0100, Johannes Schindelin wrote:
> > That is to be expected. After all, the first is a script. However, I
> > really have ask: how often per hour do you want to run that program?
>
> I have a project that needs some cleaning-up and I'd like to do it
> incrementally. I think I'll have to run it about a dozen times.
A dozen times seems not that bad, especially since you can run both
programs on a limited set of commits. So the cost is not that big.
> > I am really unhappy that so much is talked about filtering out
> > commits. That is amost certainly not what you want in most cases.
> > In particular, I suspect that most users would expect the _changes_
> > filtered out by such a command, which is just not true.
>
> I don't care about that either. I'm just mentioning it because it's
> mentioned in the git-filter-branch documentation (which you added).
Which I copied. And this is not the first, let alone the only example in
filter-branch's documentation.
> > The second is to rewrite the commit messages so that the hashes are
> > mapped, too. But that should be relatively easy, too: you can provide
> > a message filter, and you can use the provided "map" function. If
> > this seems to be what many people need, you can write a simple
> > function and put it into filter-branch for common use.
>
> It's not going to be me (as I sais, I don't like shell programming).
Yes, you made that clear.
However, this leaves things only in half-finished states.
- "git filter-branch" did not learn the useful features that you seem to
need, and
- your builtin is at most a start of a builtin replacement for
filter-branch, which changes the semantics, to be sure.
I have no doubts that it will stay that way for a while, since this
builtin seems to be good enough for what you want it to do.
Ciao,
Dscho
^ permalink raw reply
* [mingw port] Problem starting git subcommands
From: Henning Rogge @ 2007-07-08 18:26 UTC (permalink / raw)
To: git
I have experimented with GIT on my linux system successfully but one
of my projects has to be done on Windows (neither VMware Workstation
nor Wine can help at the moment) so I got a copy of the mingw port of
GIT (http://lilypond.org/git/binaries/mingw/git-1.5.2.1-1.mingw.exe).
Installation succeeded without a single error but when I tried to run
a few GIT commands I noticed a "bug":
the git.exe command does not recognize any scripted subcommand
(git-clone in my case) !
> C:\Programme\Git>git clone
> git: 'clone' is not a git-command
Do I need another software package ? I tried to install the Mingw
package itself, but it did not help at all.
^ 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