зеркало из https://github.com/microsoft/git.git
Revert "Merge branch 'js/notes'"
This reverts commit7b75b331f6
, reversing changes made to5d680a67d7
.
This commit is contained in:
Родитель
f1c8a48a2d
Коммит
954cfb5cfd
|
@ -82,7 +82,6 @@ git-mktag
|
|||
git-mktree
|
||||
git-name-rev
|
||||
git-mv
|
||||
git-notes
|
||||
git-pack-redundant
|
||||
git-pack-objects
|
||||
git-pack-refs
|
||||
|
|
|
@ -37,8 +37,6 @@ Updates since v1.6.1
|
|||
|
||||
* automatic typo correction works on aliases as well
|
||||
|
||||
* Initial support for "git notes" implemented.
|
||||
|
||||
* @{-1} is a way to refer to the last branch you were on. This is
|
||||
accepted not only where an object name is expected, but anywhere
|
||||
a branch name is expected. E.g. "git branch --track mybranch @{-1}"
|
||||
|
|
|
@ -422,19 +422,6 @@ relatively high IO latencies. With this set to 'true', git will do the
|
|||
index comparison to the filesystem data in parallel, allowing
|
||||
overlapping IO's.
|
||||
|
||||
core.notesRef::
|
||||
When showing commit messages, also show notes which are stored in
|
||||
the given ref. This ref is expected to contain files named
|
||||
after the full SHA-1 of the commit they annotate.
|
||||
+
|
||||
If such a file exists in the given ref, the referenced blob is read, and
|
||||
appended to the commit message, separated by a "Notes:" line. If the
|
||||
given ref itself does not exist, it is not an error, but means that no
|
||||
notes should be printed.
|
||||
+
|
||||
This setting defaults to "refs/notes/commits", and can be overridden by
|
||||
the `GIT_NOTES_REF` environment variable.
|
||||
|
||||
alias.*::
|
||||
Command aliases for the linkgit:git[1] command wrapper - e.g.
|
||||
after defining "alias.last = cat-file commit HEAD", the invocation
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
git-notes(1)
|
||||
============
|
||||
|
||||
NAME
|
||||
----
|
||||
git-notes - Add/inspect commit notes
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git-notes' (edit | show) [commit]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
This command allows you to add notes to commit messages, without
|
||||
changing the commit. To discern these notes from the message stored
|
||||
in the commit object, the notes are indented like the message, after
|
||||
an unindented line saying "Notes:".
|
||||
|
||||
To disable commit notes, you have to set the config variable
|
||||
core.notesRef to the empty string. Alternatively, you can set it
|
||||
to a different ref, something like "refs/notes/bugzilla". This setting
|
||||
can be overridden by the environment variable "GIT_NOTES_REF".
|
||||
|
||||
|
||||
SUBCOMMANDS
|
||||
-----------
|
||||
|
||||
edit::
|
||||
Edit the notes for a given commit (defaults to HEAD).
|
||||
|
||||
show::
|
||||
Show the notes for a given commit (defaults to HEAD).
|
||||
|
||||
|
||||
Author
|
||||
------
|
||||
Written by Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
Documentation by Johannes Schindelin
|
||||
|
||||
GIT
|
||||
---
|
||||
Part of the gitlink:git[7] suite
|
3
Makefile
3
Makefile
|
@ -265,7 +265,6 @@ SCRIPT_SH += git-merge-octopus.sh
|
|||
SCRIPT_SH += git-merge-one-file.sh
|
||||
SCRIPT_SH += git-merge-resolve.sh
|
||||
SCRIPT_SH += git-mergetool.sh
|
||||
SCRIPT_SH += git-notes.sh
|
||||
SCRIPT_SH += git-parse-remote.sh
|
||||
SCRIPT_SH += git-pull.sh
|
||||
SCRIPT_SH += git-quiltimport.sh
|
||||
|
@ -378,7 +377,6 @@ LIB_H += ll-merge.h
|
|||
LIB_H += log-tree.h
|
||||
LIB_H += mailmap.h
|
||||
LIB_H += merge-recursive.h
|
||||
LIB_H += notes.h
|
||||
LIB_H += object.h
|
||||
LIB_H += pack.h
|
||||
LIB_H += pack-refs.h
|
||||
|
@ -461,7 +459,6 @@ LIB_OBJS += match-trees.o
|
|||
LIB_OBJS += merge-file.o
|
||||
LIB_OBJS += merge-recursive.o
|
||||
LIB_OBJS += name-hash.o
|
||||
LIB_OBJS += notes.o
|
||||
LIB_OBJS += object.o
|
||||
LIB_OBJS += pack-check.o
|
||||
LIB_OBJS += pack-refs.o
|
||||
|
|
3
cache.h
3
cache.h
|
@ -371,8 +371,6 @@ static inline enum object_type object_type(unsigned int mode)
|
|||
#define GITATTRIBUTES_FILE ".gitattributes"
|
||||
#define INFOATTRIBUTES_FILE "info/attributes"
|
||||
#define ATTRIBUTE_MACRO_PREFIX "[attr]"
|
||||
#define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
|
||||
#define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
|
||||
|
||||
extern int is_bare_repository_cfg;
|
||||
extern int is_bare_repository(void);
|
||||
|
@ -544,7 +542,6 @@ enum rebase_setup_type {
|
|||
|
||||
extern enum branch_track git_branch_track;
|
||||
extern enum rebase_setup_type autorebase;
|
||||
extern char *notes_ref_name;
|
||||
|
||||
#define GIT_REPO_VERSION 0
|
||||
extern int repository_format_version;
|
||||
|
|
|
@ -73,7 +73,6 @@ git-mktag plumbingmanipulators
|
|||
git-mktree plumbingmanipulators
|
||||
git-mv mainporcelain common
|
||||
git-name-rev plumbinginterrogators
|
||||
git-notes mainporcelain
|
||||
git-pack-objects plumbingmanipulators
|
||||
git-pack-redundant plumbinginterrogators
|
||||
git-pack-refs ancillarymanipulators
|
||||
|
|
1
commit.c
1
commit.c
|
@ -5,7 +5,6 @@
|
|||
#include "utf8.h"
|
||||
#include "diff.h"
|
||||
#include "revision.h"
|
||||
#include "notes.h"
|
||||
|
||||
int save_commit_buffer = 1;
|
||||
|
||||
|
|
5
config.c
5
config.c
|
@ -469,11 +469,6 @@ static int git_default_core_config(const char *var, const char *value)
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(var, "core.notesref")) {
|
||||
notes_ref_name = xstrdup(value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(var, "core.pager"))
|
||||
return git_config_string(&pager_program, var, value);
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
|
|||
|
||||
/* Parallel index stat data preload? */
|
||||
int core_preload_index = 0;
|
||||
char *notes_ref_name;
|
||||
|
||||
/* This is set by setup_git_dir_gently() and/or git_default_config() */
|
||||
char *git_work_tree_cfg;
|
||||
|
|
65
git-notes.sh
65
git-notes.sh
|
@ -1,65 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
USAGE="(edit | show) [commit]"
|
||||
. git-sh-setup
|
||||
|
||||
test -n "$3" && usage
|
||||
|
||||
test -z "$1" && usage
|
||||
ACTION="$1"; shift
|
||||
|
||||
test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="$(git config core.notesref)"
|
||||
test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="refs/notes/commits"
|
||||
|
||||
COMMIT=$(git rev-parse --verify --default HEAD "$@") ||
|
||||
die "Invalid commit: $@"
|
||||
|
||||
MESSAGE="$GIT_DIR"/new-notes-$COMMIT
|
||||
trap '
|
||||
test -f "$MESSAGE" && rm "$MESSAGE"
|
||||
' 0
|
||||
|
||||
case "$ACTION" in
|
||||
edit)
|
||||
GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MESSAGE"
|
||||
|
||||
GIT_INDEX_FILE="$MESSAGE".idx
|
||||
export GIT_INDEX_FILE
|
||||
|
||||
CURRENT_HEAD=$(git show-ref "$GIT_NOTES_REF" | cut -f 1 -d ' ')
|
||||
if [ -z "$CURRENT_HEAD" ]; then
|
||||
PARENT=
|
||||
else
|
||||
PARENT="-p $CURRENT_HEAD"
|
||||
git read-tree "$GIT_NOTES_REF" || die "Could not read index"
|
||||
git cat-file blob :$COMMIT >> "$MESSAGE" 2> /dev/null
|
||||
fi
|
||||
|
||||
${VISUAL:-${EDITOR:-vi}} "$MESSAGE"
|
||||
|
||||
grep -v ^# < "$MESSAGE" | git stripspace > "$MESSAGE".processed
|
||||
mv "$MESSAGE".processed "$MESSAGE"
|
||||
if [ -s "$MESSAGE" ]; then
|
||||
BLOB=$(git hash-object -w "$MESSAGE") ||
|
||||
die "Could not write into object database"
|
||||
git update-index --add --cacheinfo 0644 $BLOB $COMMIT ||
|
||||
die "Could not write index"
|
||||
else
|
||||
test -z "$CURRENT_HEAD" &&
|
||||
die "Will not initialise with empty tree"
|
||||
git update-index --force-remove $COMMIT ||
|
||||
die "Could not update index"
|
||||
fi
|
||||
|
||||
TREE=$(git write-tree) || die "Could not write tree"
|
||||
NEW_HEAD=$(echo Annotate $COMMIT | git commit-tree $TREE $PARENT) ||
|
||||
die "Could not annotate"
|
||||
git update-ref -m "Annotate $COMMIT" \
|
||||
"$GIT_NOTES_REF" $NEW_HEAD $CURRENT_HEAD
|
||||
;;
|
||||
show)
|
||||
git show "$GIT_NOTES_REF":$COMMIT
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
esac
|
160
notes.c
160
notes.c
|
@ -1,160 +0,0 @@
|
|||
#include "cache.h"
|
||||
#include "commit.h"
|
||||
#include "notes.h"
|
||||
#include "refs.h"
|
||||
#include "utf8.h"
|
||||
#include "strbuf.h"
|
||||
#include "tree-walk.h"
|
||||
|
||||
struct entry {
|
||||
unsigned char commit_sha1[20];
|
||||
unsigned char notes_sha1[20];
|
||||
};
|
||||
|
||||
struct hash_map {
|
||||
struct entry *entries;
|
||||
off_t count, size;
|
||||
};
|
||||
|
||||
static int initialized;
|
||||
static struct hash_map hash_map;
|
||||
|
||||
static int hash_index(struct hash_map *map, const unsigned char *sha1)
|
||||
{
|
||||
int i = ((*(unsigned int *)sha1) % map->size);
|
||||
|
||||
for (;;) {
|
||||
unsigned char *current = map->entries[i].commit_sha1;
|
||||
|
||||
if (!hashcmp(sha1, current))
|
||||
return i;
|
||||
|
||||
if (is_null_sha1(current))
|
||||
return -1 - i;
|
||||
|
||||
if (++i == map->size)
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void add_entry(const unsigned char *commit_sha1,
|
||||
const unsigned char *notes_sha1)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (hash_map.count + 1 > hash_map.size >> 1) {
|
||||
int i, old_size = hash_map.size;
|
||||
struct entry *old = hash_map.entries;
|
||||
|
||||
hash_map.size = old_size ? old_size << 1 : 64;
|
||||
hash_map.entries = (struct entry *)
|
||||
xcalloc(sizeof(struct entry), hash_map.size);
|
||||
|
||||
for (i = 0; i < old_size; i++)
|
||||
if (!is_null_sha1(old[i].commit_sha1)) {
|
||||
index = -1 - hash_index(&hash_map,
|
||||
old[i].commit_sha1);
|
||||
memcpy(hash_map.entries + index, old + i,
|
||||
sizeof(struct entry));
|
||||
}
|
||||
free(old);
|
||||
}
|
||||
|
||||
index = hash_index(&hash_map, commit_sha1);
|
||||
if (index < 0) {
|
||||
index = -1 - index;
|
||||
hash_map.count++;
|
||||
}
|
||||
|
||||
hashcpy(hash_map.entries[index].commit_sha1, commit_sha1);
|
||||
hashcpy(hash_map.entries[index].notes_sha1, notes_sha1);
|
||||
}
|
||||
|
||||
static void initialize_hash_map(const char *notes_ref_name)
|
||||
{
|
||||
unsigned char sha1[20], commit_sha1[20];
|
||||
unsigned mode;
|
||||
struct tree_desc desc;
|
||||
struct name_entry entry;
|
||||
void *buf;
|
||||
|
||||
if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) ||
|
||||
get_tree_entry(commit_sha1, "", sha1, &mode))
|
||||
return;
|
||||
|
||||
buf = fill_tree_descriptor(&desc, sha1);
|
||||
if (!buf)
|
||||
die("Could not read %s for notes-index", sha1_to_hex(sha1));
|
||||
|
||||
while (tree_entry(&desc, &entry))
|
||||
if (!get_sha1(entry.path, commit_sha1))
|
||||
add_entry(commit_sha1, entry.sha1);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static unsigned char *lookup_notes(const unsigned char *commit_sha1)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (!hash_map.size)
|
||||
return NULL;
|
||||
|
||||
index = hash_index(&hash_map, commit_sha1);
|
||||
if (index < 0)
|
||||
return NULL;
|
||||
return hash_map.entries[index].notes_sha1;
|
||||
}
|
||||
|
||||
void get_commit_notes(const struct commit *commit, struct strbuf *sb,
|
||||
const char *output_encoding)
|
||||
{
|
||||
static const char *utf8 = "utf-8";
|
||||
unsigned char *sha1;
|
||||
char *msg, *msg_p;
|
||||
unsigned long linelen, msglen;
|
||||
enum object_type type;
|
||||
|
||||
if (!initialized) {
|
||||
const char *env = getenv(GIT_NOTES_REF_ENVIRONMENT);
|
||||
if (env)
|
||||
notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT);
|
||||
else if (!notes_ref_name)
|
||||
notes_ref_name = GIT_NOTES_DEFAULT_REF;
|
||||
initialize_hash_map(notes_ref_name);
|
||||
initialized = 1;
|
||||
}
|
||||
|
||||
sha1 = lookup_notes(commit->object.sha1);
|
||||
if (!sha1)
|
||||
return;
|
||||
|
||||
if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen ||
|
||||
type != OBJ_BLOB)
|
||||
return;
|
||||
|
||||
if (output_encoding && *output_encoding &&
|
||||
strcmp(utf8, output_encoding)) {
|
||||
char *reencoded = reencode_string(msg, output_encoding, utf8);
|
||||
if (reencoded) {
|
||||
free(msg);
|
||||
msg = reencoded;
|
||||
msglen = strlen(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/* we will end the annotation by a newline anyway */
|
||||
if (msglen && msg[msglen - 1] == '\n')
|
||||
msglen--;
|
||||
|
||||
strbuf_addstr(sb, "\nNotes:\n");
|
||||
|
||||
for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
|
||||
linelen = strchrnul(msg_p, '\n') - msg_p;
|
||||
|
||||
strbuf_addstr(sb, " ");
|
||||
strbuf_add(sb, msg_p, linelen);
|
||||
strbuf_addch(sb, '\n');
|
||||
}
|
||||
|
||||
free(msg);
|
||||
}
|
7
notes.h
7
notes.h
|
@ -1,7 +0,0 @@
|
|||
#ifndef NOTES_H
|
||||
#define NOTES_H
|
||||
|
||||
void get_commit_notes(const struct commit *commit, struct strbuf *sb,
|
||||
const char *output_encoding);
|
||||
|
||||
#endif
|
5
pretty.c
5
pretty.c
|
@ -6,7 +6,6 @@
|
|||
#include "string-list.h"
|
||||
#include "mailmap.h"
|
||||
#include "log-tree.h"
|
||||
#include "notes.h"
|
||||
#include "color.h"
|
||||
|
||||
static char *user_format;
|
||||
|
@ -921,9 +920,5 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
|
|||
*/
|
||||
if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
|
||||
strbuf_addch(sb, '\n');
|
||||
|
||||
if (fmt != CMIT_FMT_ONELINE)
|
||||
get_commit_notes(commit, sb, encoding);
|
||||
|
||||
free(reencoded);
|
||||
}
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2007 Johannes E. Schindelin
|
||||
#
|
||||
|
||||
test_description='Test commit notes'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
cat > fake_editor.sh << \EOF
|
||||
echo "$MSG" > "$1"
|
||||
echo "$MSG" >& 2
|
||||
EOF
|
||||
chmod a+x fake_editor.sh
|
||||
VISUAL=./fake_editor.sh
|
||||
export VISUAL
|
||||
|
||||
test_expect_success 'cannot annotate non-existing HEAD' '
|
||||
! MSG=3 git notes edit
|
||||
'
|
||||
|
||||
test_expect_success setup '
|
||||
: > a1 &&
|
||||
git add a1 &&
|
||||
test_tick &&
|
||||
git commit -m 1st &&
|
||||
: > a2 &&
|
||||
git add a2 &&
|
||||
test_tick &&
|
||||
git commit -m 2nd
|
||||
'
|
||||
|
||||
test_expect_success 'need valid notes ref' '
|
||||
! MSG=1 GIT_NOTES_REF='/' git notes edit &&
|
||||
! MSG=2 GIT_NOTES_REF='/' git notes show
|
||||
'
|
||||
|
||||
test_expect_success 'create notes' '
|
||||
git config core.notesRef refs/notes/commits &&
|
||||
MSG=b1 git notes edit &&
|
||||
test ! -f .git/new-notes &&
|
||||
test 1 = $(git ls-tree refs/notes/commits | wc -l) &&
|
||||
test b1 = $(git notes show) &&
|
||||
git show HEAD^ &&
|
||||
! git notes show HEAD^
|
||||
'
|
||||
|
||||
cat > expect << EOF
|
||||
commit 268048bfb8a1fb38e703baceb8ab235421bf80c5
|
||||
Author: A U Thor <author@example.com>
|
||||
Date: Thu Apr 7 15:14:13 2005 -0700
|
||||
|
||||
2nd
|
||||
|
||||
Notes:
|
||||
b1
|
||||
EOF
|
||||
|
||||
test_expect_success 'show notes' '
|
||||
! (git cat-file commit HEAD | grep b1) &&
|
||||
git log -1 > output &&
|
||||
test_cmp expect output
|
||||
'
|
||||
test_expect_success 'create multi-line notes (setup)' '
|
||||
: > a3 &&
|
||||
git add a3 &&
|
||||
test_tick &&
|
||||
git commit -m 3rd &&
|
||||
MSG="b3
|
||||
c3c3c3c3
|
||||
d3d3d3" git notes edit
|
||||
'
|
||||
|
||||
cat > expect-multiline << EOF
|
||||
commit 1584215f1d29c65e99c6c6848626553fdd07fd75
|
||||
Author: A U Thor <author@example.com>
|
||||
Date: Thu Apr 7 15:15:13 2005 -0700
|
||||
|
||||
3rd
|
||||
|
||||
Notes:
|
||||
b3
|
||||
c3c3c3c3
|
||||
d3d3d3
|
||||
EOF
|
||||
|
||||
printf "\n" >> expect-multiline
|
||||
cat expect >> expect-multiline
|
||||
|
||||
test_expect_success 'show multi-line notes' '
|
||||
git log -2 > output &&
|
||||
test_cmp expect-multiline output
|
||||
'
|
||||
|
||||
test_done
|
|
@ -1,98 +0,0 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2007 Johannes E. Schindelin
|
||||
#
|
||||
|
||||
test_description='Test commit notes index (expensive!)'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test -z "$GIT_NOTES_TIMING_TESTS" && {
|
||||
say Skipping timing tests
|
||||
test_done
|
||||
exit
|
||||
}
|
||||
|
||||
create_repo () {
|
||||
number_of_commits=$1
|
||||
nr=0
|
||||
parent=
|
||||
test -d .git || {
|
||||
git init &&
|
||||
tree=$(git write-tree) &&
|
||||
while [ $nr -lt $number_of_commits ]; do
|
||||
test_tick &&
|
||||
commit=$(echo $nr | git commit-tree $tree $parent) ||
|
||||
return
|
||||
parent="-p $commit"
|
||||
nr=$(($nr+1))
|
||||
done &&
|
||||
git update-ref refs/heads/master $commit &&
|
||||
{
|
||||
export GIT_INDEX_FILE=.git/temp;
|
||||
git rev-list HEAD | cat -n | sed "s/^[ ][ ]*/ /g" |
|
||||
while read nr sha1; do
|
||||
blob=$(echo note $nr | git hash-object -w --stdin) &&
|
||||
echo $sha1 | sed "s/^/0644 $blob 0 /"
|
||||
done | git update-index --index-info &&
|
||||
tree=$(git write-tree) &&
|
||||
test_tick &&
|
||||
commit=$(echo notes | git commit-tree $tree) &&
|
||||
git update-ref refs/notes/commits $commit
|
||||
} &&
|
||||
git config core.notesRef refs/notes/commits
|
||||
}
|
||||
}
|
||||
|
||||
test_notes () {
|
||||
count=$1 &&
|
||||
git config core.notesRef refs/notes/commits &&
|
||||
git log | grep "^ " > output &&
|
||||
i=1 &&
|
||||
while [ $i -le $count ]; do
|
||||
echo " $(($count-$i))" &&
|
||||
echo " note $i" &&
|
||||
i=$(($i+1));
|
||||
done > expect &&
|
||||
git diff expect output
|
||||
}
|
||||
|
||||
cat > time_notes << \EOF
|
||||
mode=$1
|
||||
i=1
|
||||
while [ $i -lt $2 ]; do
|
||||
case $1 in
|
||||
no-notes)
|
||||
export GIT_NOTES_REF=non-existing
|
||||
;;
|
||||
notes)
|
||||
unset GIT_NOTES_REF
|
||||
;;
|
||||
esac
|
||||
git log >/dev/null
|
||||
i=$(($i+1))
|
||||
done
|
||||
EOF
|
||||
|
||||
time_notes () {
|
||||
for mode in no-notes notes
|
||||
do
|
||||
echo $mode
|
||||
/usr/bin/time sh ../time_notes $mode $1
|
||||
done
|
||||
}
|
||||
|
||||
for count in 10 100 1000 10000; do
|
||||
|
||||
mkdir $count
|
||||
(cd $count;
|
||||
|
||||
test_expect_success "setup $count" "create_repo $count"
|
||||
|
||||
test_expect_success 'notes work' "test_notes $count"
|
||||
|
||||
test_expect_success 'notes timing' "time_notes 100"
|
||||
)
|
||||
done
|
||||
|
||||
test_done
|
Загрузка…
Ссылка в новой задаче